chore(medusa): cleanup medusa package (#7206)
This commit is contained in:
committed by
GitHub
parent
8b61dccd0f
commit
71f4f46cb9
@@ -9,9 +9,7 @@
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": [
|
||||
"integration-tests-api",
|
||||
"integration-tests-plugins",
|
||||
"integration-tests-modules",
|
||||
"integration-tests-repositories",
|
||||
"@medusajs/dashboard",
|
||||
"@medusajs/admin-shared",
|
||||
"@medusajs/admin-bundler",
|
||||
|
||||
4
.github/actions/cache-deps/action.yml
vendored
4
.github/actions/cache-deps/action.yml
vendored
@@ -11,9 +11,9 @@ runs:
|
||||
with:
|
||||
path: |
|
||||
.yarn/cache
|
||||
key: ${{ runner.os }}-yarn-${{inputs.extension}}-v8-${{ hashFiles('**/yarn.lock') }}
|
||||
key: ${{ runner.os }}-yarn-${{inputs.extension}}-v8-${{ hashFiles('**/yarn.lock') }}-3
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-${{inputs.extension}}-v8
|
||||
${{ runner.os }}-yarn-${{inputs.extension}}-v8-3
|
||||
# We want to only bootstrap and install if no cache is found.
|
||||
- run: yarn install --immutable
|
||||
shell: bash
|
||||
|
||||
225
.github/workflows/action.yml
vendored
225
.github/workflows/action.yml
vendored
@@ -2,12 +2,12 @@ name: Medusa Pipeline
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
- v1.x
|
||||
- develop
|
||||
- v1.x
|
||||
pull_request:
|
||||
branches:
|
||||
- develop
|
||||
- v1.x
|
||||
- develop
|
||||
- v1.x
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
@@ -157,75 +157,75 @@ jobs:
|
||||
DB_PASSWORD: postgres
|
||||
DB_USERNAME: postgres
|
||||
|
||||
integration-tests-api-matrix:
|
||||
needs: setup
|
||||
name: Shard (${{ matrix.chunk }}) API Integration Tests
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
chunk: ${{ fromJSON(needs.setup.outputs.api-matrix) }}
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
#integration-tests-api-matrix:
|
||||
# needs: setup
|
||||
# name: Shard (${{ matrix.chunk }}) API Integration Tests
|
||||
# runs-on: ubuntu-latest
|
||||
# strategy:
|
||||
# fail-fast: false
|
||||
# matrix:
|
||||
# chunk: ${{ fromJSON(needs.setup.outputs.api-matrix) }}
|
||||
# env:
|
||||
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
#
|
||||
# services:
|
||||
# redis:
|
||||
# image: redis
|
||||
# options: >-
|
||||
# --health-cmd "redis-cli ping"
|
||||
# --health-interval 1s
|
||||
# --health-timeout 10s
|
||||
# --health-retries 10
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# env:
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# POSTGRES_USER: postgres
|
||||
# options: >-
|
||||
# --health-cmd pg_isready
|
||||
# --health-interval 1s
|
||||
# --health-timeout 10s
|
||||
# --health-retries 10
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 1s
|
||||
--health-timeout 10s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 6379:6379
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 1s
|
||||
--health-timeout 10s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 5432:5432
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
#
|
||||
# - name: Install dependencies
|
||||
# uses: ./.github/actions/cache-deps
|
||||
# with:
|
||||
# extension: pipeline
|
||||
#
|
||||
# - name: Run API integration tests
|
||||
# run: yarn test:integration:api
|
||||
# env:
|
||||
# DB_USERNAME: postgres
|
||||
# DB_PASSWORD: postgres
|
||||
# NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
# CHUNK: ${{ matrix.chunk }}
|
||||
# CHUNKS: ${{ needs.setup.outputs.api-chunks }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/cache-deps
|
||||
with:
|
||||
extension: pipeline
|
||||
|
||||
- name: Run API integration tests
|
||||
run: yarn test:integration:api
|
||||
env:
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
CHUNK: ${{ matrix.chunk }}
|
||||
CHUNKS: ${{ needs.setup.outputs.api-chunks }}
|
||||
|
||||
integration-tests-api:
|
||||
if: ${{ always() }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: integration-tests-api-matrix
|
||||
steps:
|
||||
- run: exit 1
|
||||
if: >-
|
||||
${{
|
||||
contains(needs.integration-tests-api-matrix.result, 'failure')
|
||||
|| contains(needs.integration-tests-api-matrix.result, 'cancelled')
|
||||
|| contains(needs.integration-tests-api-matrix.result, 'skipped')
|
||||
}}
|
||||
- run: exit 0
|
||||
if: ${{ contains(needs.integration-tests-api-matrix.result, 'success') }}
|
||||
#integration-tests-api:
|
||||
# if: ${{ always() }}
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: integration-tests-api-matrix
|
||||
# steps:
|
||||
# - run: exit 1
|
||||
# if: >-
|
||||
# ${{
|
||||
# contains(needs.integration-tests-api-matrix.result, 'failure')
|
||||
# || contains(needs.integration-tests-api-matrix.result, 'cancelled')
|
||||
# || contains(needs.integration-tests-api-matrix.result, 'skipped')
|
||||
# }}
|
||||
# - run: exit 0
|
||||
# if: ${{ contains(needs.integration-tests-api-matrix.result, 'success') }}
|
||||
|
||||
unit-tests:
|
||||
if: ${{ always() }}
|
||||
@@ -242,51 +242,6 @@ jobs:
|
||||
- run: exit 0
|
||||
if: ${{ contains(needs.unit-tests-matrix.result, 'success') }}
|
||||
|
||||
integration-tests-plugins:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 1s
|
||||
--health-timeout 10s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Node.js environment
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "16.10.0"
|
||||
cache: "yarn"
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/cache-deps
|
||||
with:
|
||||
extension: pipeline
|
||||
|
||||
- name: Run plugin integration tests
|
||||
run: yarn test:integration:plugins
|
||||
env:
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
NODE_OPTIONS: "--max_old_space_size=4096"
|
||||
|
||||
integration-tests-modules-matrix:
|
||||
needs: setup
|
||||
name: Shard (${{ matrix.chunk }}) Module Integration Tests
|
||||
@@ -346,41 +301,3 @@ jobs:
|
||||
}}
|
||||
- run: exit 0
|
||||
if: ${{ contains(needs.integration-tests-modules-matrix.result, 'success') }}
|
||||
|
||||
integration-tests-repositories:
|
||||
needs: setup
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 1s
|
||||
--health-timeout 10s
|
||||
--health-retries 10
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
uses: ./.github/actions/cache-deps
|
||||
with:
|
||||
extension: pipeline
|
||||
|
||||
- name: Run repository integration tests
|
||||
run: yarn test:integration:repositories
|
||||
env:
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: postgres
|
||||
|
||||
180
.github/workflows/test-cli-with-database.yml
vendored
180
.github/workflows/test-cli-with-database.yml
vendored
@@ -1,90 +1,90 @@
|
||||
name: CLI Pipeline
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test-cli-with-database:
|
||||
env:
|
||||
NODE_ENV: CI
|
||||
REDIS_URL: redis://localhost:6379
|
||||
DATABASE_URL: "postgres://postgres:postgres@localhost/cli-test"
|
||||
services:
|
||||
redis:
|
||||
image: redis
|
||||
# Set health checks to wait until redis has started
|
||||
options: >-
|
||||
--health-cmd "redis-cli ping"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 6379:6379
|
||||
|
||||
postgres:
|
||||
image: postgres
|
||||
env:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_USER: postgres
|
||||
POSTGRES_DB: cli-test
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
ports:
|
||||
- 5432:5432
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup development server
|
||||
uses: ./.github/actions/setup-server
|
||||
with:
|
||||
cache-extension: "cli-test"
|
||||
node-version: "16.14"
|
||||
|
||||
- name: Install Medusa cli
|
||||
run: npm i -g @medusajs/medusa-cli
|
||||
|
||||
- name: Create Medusa project
|
||||
run: |
|
||||
medusa new cli-test --skip-db
|
||||
working-directory: ..
|
||||
|
||||
- name: run medusa dev
|
||||
run: medusa-dev --force-install
|
||||
working-directory: ../cli-test
|
||||
|
||||
- name: Run migrations
|
||||
run: medusa migrations run
|
||||
working-directory: ../cli-test
|
||||
|
||||
- name: Seed db
|
||||
run: yarn seed
|
||||
working-directory: ../cli-test
|
||||
|
||||
- name: Create admin user
|
||||
run: medusa user -e test@test.com -p password -i admin_123
|
||||
working-directory: ../cli-test
|
||||
|
||||
########################## Test medusa develop ###############################
|
||||
|
||||
- name: Run development server
|
||||
run: medusa develop &
|
||||
working-directory: ../cli-test
|
||||
|
||||
- name: Testing development server
|
||||
uses: ./.github/actions/test-server
|
||||
|
||||
########################### Test medusa start ################################
|
||||
|
||||
- name: Starting medusa
|
||||
run: medusa start &
|
||||
working-directory: ../cli-test
|
||||
|
||||
- name: Testing server
|
||||
uses: ./.github/actions/test-server
|
||||
#name: CLI Pipeline
|
||||
#on:
|
||||
# pull_request:
|
||||
#
|
||||
#jobs:
|
||||
# test-cli-with-database:
|
||||
# env:
|
||||
# NODE_ENV: CI
|
||||
# REDIS_URL: redis://localhost:6379
|
||||
# DATABASE_URL: "postgres://postgres:postgres@localhost/cli-test"
|
||||
# services:
|
||||
# redis:
|
||||
# image: redis
|
||||
# Set health checks to wait until redis has started
|
||||
# options: >-
|
||||
# --health-cmd "redis-cli ping"
|
||||
# --health-interval 10s
|
||||
# --health-timeout 5s
|
||||
# --health-retries 5
|
||||
# ports:
|
||||
# - 6379:6379
|
||||
#
|
||||
# postgres:
|
||||
# image: postgres
|
||||
# env:
|
||||
# POSTGRES_PASSWORD: postgres
|
||||
# POSTGRES_USER: postgres
|
||||
# POSTGRES_DB: cli-test
|
||||
# options: >-
|
||||
# --health-cmd pg_isready
|
||||
# --health-interval 10s
|
||||
# --health-timeout 5s
|
||||
# --health-retries 5
|
||||
# ports:
|
||||
# - 5432:5432
|
||||
#
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout
|
||||
# uses: actions/checkout@v3
|
||||
# with:
|
||||
# fetch-depth: 0
|
||||
#
|
||||
# - name: Setup development server
|
||||
# uses: ./.github/actions/setup-server
|
||||
# with:
|
||||
# cache-extension: "cli-test"
|
||||
# node-version: "16.14"
|
||||
#
|
||||
# - name: Install Medusa cli
|
||||
# run: npm i -g @medusajs/medusa-cli
|
||||
#
|
||||
# - name: Create Medusa project
|
||||
# run: |
|
||||
# medusa new cli-test --skip-db
|
||||
# working-directory: ..
|
||||
#
|
||||
# - name: run medusa dev
|
||||
# run: medusa-dev --force-install
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# - name: Run migrations
|
||||
# run: medusa migrations run
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# - name: Seed db
|
||||
# run: yarn seed
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# - name: Create admin user
|
||||
# run: medusa user -e test@test.com -p password -i admin_123
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# Test medusa develop
|
||||
#
|
||||
# - name: Run development server
|
||||
# run: medusa develop &
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# - name: Testing development server
|
||||
# uses: ./.github/actions/test-server
|
||||
#
|
||||
# Test medusa start
|
||||
#
|
||||
# - name: Starting medusa
|
||||
# run: medusa start &
|
||||
# working-directory: ../cli-test
|
||||
#
|
||||
# - name: Testing server
|
||||
# uses: ./.github/actions/test-server
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import { AbstractFileService } from "@medusajs/medusa"
|
||||
import * as fs from "fs"
|
||||
import mkdirp from "mkdirp"
|
||||
import { resolve } from "path"
|
||||
import stream from "stream"
|
||||
|
||||
export default class LocalFileService extends AbstractFileService {
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
this.upload_dir_ =
|
||||
process.env.UPLOAD_DIR ?? options.upload_dir ?? "uploads/images"
|
||||
|
||||
if (!fs.existsSync(this.upload_dir_)) {
|
||||
fs.mkdirSync(this.upload_dir_)
|
||||
}
|
||||
}
|
||||
|
||||
upload(file) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, file.originalname)
|
||||
|
||||
let content = ""
|
||||
if (file.filename) {
|
||||
content = fs.readFileSync(
|
||||
resolve(process.cwd(), "uploads", file.filename)
|
||||
)
|
||||
}
|
||||
|
||||
const pathSegments = path.split("/")
|
||||
pathSegments.splice(-1)
|
||||
const dirname = pathSegments.join("/")
|
||||
mkdirp.sync(dirname, { recursive: true })
|
||||
|
||||
fs.writeFile(path, content.toString(), (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise({ url: path })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
delete({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
fs.unlink(path, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise("file unlinked")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getUploadStreamDescriptor({ name, ext }) {
|
||||
const fileKey = `${name}.${ext}`
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
|
||||
const isFileExists = fs.existsSync(path)
|
||||
if (!isFileExists) {
|
||||
await this.upload({ originalname: fileKey })
|
||||
}
|
||||
|
||||
const pass = new stream.PassThrough()
|
||||
pass.pipe(fs.createWriteStream(path))
|
||||
|
||||
return {
|
||||
writeStream: pass,
|
||||
promise: Promise.resolve(),
|
||||
url: `${this.upload_dir_}/${fileKey}`,
|
||||
fileKey,
|
||||
}
|
||||
}
|
||||
|
||||
async getDownloadStream({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
try {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
const data = fs.readFileSync(path)
|
||||
const readable = stream.Readable()
|
||||
readable._read = function () {}
|
||||
readable.push(data.toString())
|
||||
readable.push(null)
|
||||
resolvePromise(readable)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getPresignedDownloadUrl({ fileKey }) {
|
||||
return `${this.upload_dir_}/${fileKey}`
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class TestFulService extends FulfillmentService {
|
||||
static identifier = "test-ful"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "manual-fulfillment",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
validateFulfillmentData(data, cart) {
|
||||
return data
|
||||
}
|
||||
|
||||
validateOption(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
canCalculate() {
|
||||
return true
|
||||
}
|
||||
|
||||
calculatePrice(data) {
|
||||
return data.price
|
||||
}
|
||||
|
||||
createOrder() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createReturn() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createFulfillment() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
cancelFulfillment() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
|
||||
export default TestFulService
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NotificationService } from "medusa-interfaces"
|
||||
|
||||
class TestNotiService extends NotificationService {
|
||||
static identifier = "test-not"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async sendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
async resendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
export default TestNotiService
|
||||
@@ -1,87 +0,0 @@
|
||||
import { AbstractPaymentService } from "@medusajs/medusa"
|
||||
|
||||
class TestPayService extends AbstractPaymentService {
|
||||
static identifier = "test-pay"
|
||||
|
||||
constructor(_) {
|
||||
super(_)
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return []
|
||||
}
|
||||
|
||||
async createPayment(cart) {
|
||||
const fields = [
|
||||
"total",
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"shipping_total",
|
||||
"gift_card_total",
|
||||
]
|
||||
|
||||
const data = {}
|
||||
for (const k of fields) {
|
||||
data[k] = cart[k]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async createPaymentNew(inputData) {
|
||||
return inputData
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPaymentData(sessionData) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
if (
|
||||
sessionData.cart_id === "cart-id-tax-line-testing-for-pending-payment"
|
||||
) {
|
||||
return { data: {}, status: "pending" }
|
||||
}
|
||||
|
||||
return { data: {}, status: "authorized" }
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePayment(sessionData, cart) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(payment) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default TestPayService
|
||||
@@ -1,7 +0,0 @@
|
||||
# Default postgres credentials
|
||||
DB_HOST=localhost
|
||||
DB_USERNAME=postgres
|
||||
DB_PASSWORD=''
|
||||
DB_NAME=development
|
||||
|
||||
SERVER_PORT=9000
|
||||
@@ -1,31 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
require("dotenv").config({ path: path.join(__dirname, ".env.development") })
|
||||
|
||||
const { initDb } = require("./use-db-development")
|
||||
|
||||
require("./dev-require")
|
||||
|
||||
const seedDB = async (db) => {
|
||||
const seeder = require("./database/index.js")
|
||||
try {
|
||||
await seeder(db)
|
||||
} catch (err) {
|
||||
console.log("Error", err)
|
||||
}
|
||||
}
|
||||
|
||||
const start = async () => {
|
||||
console.log("Creating DB...")
|
||||
const dbConnection = await initDb()
|
||||
console.log("Creating DB. DONE")
|
||||
|
||||
console.log("Seeding DB...")
|
||||
await seedDB(dbConnection)
|
||||
console.log("Seeding DB... DONE")
|
||||
|
||||
await dbConnection.close()
|
||||
process.exit()
|
||||
}
|
||||
|
||||
start()
|
||||
@@ -1,17 +0,0 @@
|
||||
const { Customer } = require("@medusajs/medusa")
|
||||
|
||||
module.exports = async (connection) => {
|
||||
const manager = connection.manager
|
||||
|
||||
const customer = manager.create(Customer, {
|
||||
id: "customer-1",
|
||||
email: "test1@email.com",
|
||||
first_name: "John",
|
||||
last_name: "Doe",
|
||||
password_hash:
|
||||
"c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN",
|
||||
// password matching "test"
|
||||
has_account: true,
|
||||
})
|
||||
await manager.save(customer)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
const user = require("./user")
|
||||
const region = require("./region")
|
||||
const customer = require("./customer")
|
||||
|
||||
module.exports = async (db) => {
|
||||
await user(db)
|
||||
await region(db)
|
||||
await customer(db)
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
const { Region } = require("@medusajs/medusa")
|
||||
|
||||
module.exports = async (connection) => {
|
||||
const manager = connection.manager
|
||||
|
||||
const r = manager.create(Region, {
|
||||
id: "test-region",
|
||||
name: "Test Region",
|
||||
payment_providers: [{ id: "test-pay" }],
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
})
|
||||
|
||||
await manager.save(r)
|
||||
|
||||
const europeRegion = manager.create(Region, {
|
||||
id: "eur-region",
|
||||
name: "Europe Region",
|
||||
payment_providers: [{ id: "test-pay" }],
|
||||
currency_code: "eur",
|
||||
tax_rate: 0,
|
||||
})
|
||||
|
||||
await manager.save(europeRegion)
|
||||
|
||||
// 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'`
|
||||
)
|
||||
await manager.query(
|
||||
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
|
||||
)
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
const Scrypt = require("scrypt-kdf")
|
||||
const { User } = require("@medusajs/medusa")
|
||||
|
||||
module.exports = async (connection) => {
|
||||
const manager = connection.manager
|
||||
|
||||
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",
|
||||
email: "admin@medusa.js",
|
||||
api_token: "test_token",
|
||||
role: "admin",
|
||||
password_hash,
|
||||
})
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
return
|
||||
}
|
||||
const path = require("path")
|
||||
|
||||
const Module = require("module")
|
||||
const originalRequire = Module.prototype.require
|
||||
const medusaCore = path.resolve(path.join(__dirname, "../../packages"))
|
||||
|
||||
function replacePath(requirePath, pack, concatPackage = true) {
|
||||
const idx = requirePath.indexOf(pack)
|
||||
const packPath = requirePath.substring(idx + pack.length).replace(/\\/g, "/")
|
||||
|
||||
let newPath =
|
||||
medusaCore +
|
||||
"/" +
|
||||
(concatPackage ? pack + "/" : "") +
|
||||
packPath.replace("/dist", "/src").replace(".js", "")
|
||||
|
||||
if (!newPath.includes("/src")) {
|
||||
newPath += "/src"
|
||||
}
|
||||
|
||||
return path.resolve(newPath)
|
||||
}
|
||||
|
||||
function checkAndReplacePaths(path) {
|
||||
const interfaces = "medusa-interfaces"
|
||||
const utils = "medusa-core-utils"
|
||||
const base = "@medusajs"
|
||||
|
||||
if (path.includes(base)) {
|
||||
path = replacePath(path, base, false)
|
||||
} else if (path.includes(interfaces)) {
|
||||
path = replacePath(path, interfaces)
|
||||
} else if (path.includes(utils)) {
|
||||
path = replacePath(path, utils)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
Module.prototype.require = function (...args) {
|
||||
args[0] = checkAndReplacePaths(args[0])
|
||||
|
||||
if (args[0] === "glob") {
|
||||
const glob = originalRequire.apply(this, args)
|
||||
const originalGlobSync = glob.sync
|
||||
glob.GlobSync = glob.sync = (pattern, options) => {
|
||||
if (pattern.endsWith(".js") || pattern.endsWith(".ts")) {
|
||||
pattern = checkAndReplacePaths(pattern)
|
||||
pattern = pattern.replace(".js", ".{j,t}s").replace("/dist/", "/src/")
|
||||
}
|
||||
|
||||
return originalGlobSync.apply(this, [pattern, options])
|
||||
}
|
||||
return glob
|
||||
} else if (args[0] === "resolve-cwd") {
|
||||
const resolveCwd = originalRequire.apply(this, args)
|
||||
const newResolveCwd = (pattern) => {
|
||||
pattern = checkAndReplacePaths(pattern)
|
||||
|
||||
return resolveCwd.apply(this, [pattern])
|
||||
}
|
||||
return newResolveCwd
|
||||
}
|
||||
|
||||
return originalRequire.apply(this, args)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = process.env.DB_NAME
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
resolve: `./packages/medusa-payment-stripe`,
|
||||
options: {
|
||||
api_key: "api_key",
|
||||
webhook_secret: "api_key",
|
||||
},
|
||||
},
|
||||
],
|
||||
projectConfig: {
|
||||
redis_url: process.env.REDIS_URL,
|
||||
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`,
|
||||
database_type: "postgres",
|
||||
jwt_secret: "test",
|
||||
cookie_secret: "test",
|
||||
},
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
const path = require("path")
|
||||
const express = require("express")
|
||||
const importFrom = require("import-from")
|
||||
const chokidar = require("chokidar")
|
||||
const { WorkflowManager } = require("@medusajs/orchestration")
|
||||
|
||||
process.env.DEV_MODE = !!process[Symbol.for("ts-node.register.instance")]
|
||||
process.env.NODE_ENV = process.env.DEV_MODE && "development"
|
||||
|
||||
require("dotenv").config({ path: path.join(__dirname, ".env.development") })
|
||||
|
||||
require("./dev-require")
|
||||
|
||||
const medusaCore = path
|
||||
.resolve(path.join(__dirname, "../../packages"))
|
||||
.replace(/\\/g, "/")
|
||||
|
||||
let WATCHING = false
|
||||
let IS_RELOADING = false
|
||||
|
||||
function getParentModulesIds(element) {
|
||||
if (!element) {
|
||||
return []
|
||||
}
|
||||
|
||||
const ids = [element.id]
|
||||
let parent = element.parent
|
||||
while (parent && parent.id.replace(/\\/g, "/").includes(medusaCore)) {
|
||||
ids.push(parent.id)
|
||||
parent = parent.parent
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
const watchFiles = () => {
|
||||
if (WATCHING) {
|
||||
return
|
||||
}
|
||||
WATCHING = true
|
||||
|
||||
const watcher = chokidar.watch(medusaCore, {
|
||||
ignored: (rawPath) => {
|
||||
const path = rawPath.replace(/\\/g, "/")
|
||||
if (
|
||||
path.includes("/node_modules") ||
|
||||
path.includes("/dist") ||
|
||||
path.includes("/__") ||
|
||||
(/\..*/i.test(path) &&
|
||||
!(
|
||||
path.endsWith(".js") ||
|
||||
path.endsWith(".ts") ||
|
||||
path.includes("/src")
|
||||
))
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
})
|
||||
|
||||
watcher.on("change", async function (rawFile) {
|
||||
if (IS_RELOADING) {
|
||||
return
|
||||
}
|
||||
|
||||
console.log("Reloading server...")
|
||||
IS_RELOADING = true
|
||||
const start = Date.now()
|
||||
|
||||
const file = rawFile.replace(/\\/g, "/")
|
||||
|
||||
if (file.includes("/models") || file.includes("/repositories")) {
|
||||
Object.keys(require.cache).forEach(function (id) {
|
||||
const name = require.cache[id].filename
|
||||
if (!name.includes("typeorm")) {
|
||||
return
|
||||
}
|
||||
|
||||
delete require.cache[id]
|
||||
})
|
||||
}
|
||||
|
||||
const allModules = Object.keys(module.constructor._cache)
|
||||
const path = file.split("/")
|
||||
const src = path.findIndex((folder) => folder === "src")
|
||||
const next = path.slice(0, src + 2).join("/")
|
||||
|
||||
for (const rawName of allModules) {
|
||||
const name = rawName.replace(/\\/g, "/")
|
||||
if (name.includes("typeorm")) {
|
||||
delete module.constructor._cache[rawName]
|
||||
} else if (name.includes(medusaCore)) {
|
||||
if (
|
||||
name.includes("repositories") ||
|
||||
name.includes("loaders") ||
|
||||
next.endsWith(".js") ||
|
||||
next.endsWith(".ts") ||
|
||||
name.startsWith(next)
|
||||
) {
|
||||
const cacheToClean = getParentModulesIds(
|
||||
module.constructor._cache[rawName]
|
||||
)
|
||||
for (const id of cacheToClean) {
|
||||
delete module.constructor._cache[id]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WorkflowManager.unregisterAll()
|
||||
|
||||
await bootstrapApp()
|
||||
IS_RELOADING = false
|
||||
|
||||
console.log("Server reloaded in", Date.now() - start, "ms")
|
||||
})
|
||||
}
|
||||
|
||||
let server
|
||||
const bootstrapApp = async () => {
|
||||
if (server) {
|
||||
server.close()
|
||||
}
|
||||
|
||||
const app = express()
|
||||
app.use((req, res, next) => {
|
||||
res.header("Access-Control-Allow-Origin", req.headers.origin)
|
||||
res.header("Access-Control-Allow-Methods", "*")
|
||||
res.header(
|
||||
"Access-Control-Allow-Headers",
|
||||
"Origin, X-Requested-With, Content-Type, Accept"
|
||||
)
|
||||
next()
|
||||
})
|
||||
|
||||
const dir = path.resolve(
|
||||
path.join(__dirname, "../../packages/medusa/src/loaders")
|
||||
)
|
||||
const loaders = importFrom(dir, ".").default
|
||||
|
||||
const configDir = __dirname
|
||||
const { dbConnection } = await loaders({
|
||||
directory: configDir,
|
||||
expressApp: app,
|
||||
})
|
||||
|
||||
const port = process.env.SERVER_PORT ?? 9000
|
||||
server = app.listen(port, (err) => {
|
||||
watchFiles()
|
||||
console.log(`Server Running at localhost:${port}`)
|
||||
})
|
||||
}
|
||||
|
||||
void bootstrapApp()
|
||||
@@ -1,96 +0,0 @@
|
||||
import { AbstractFileService } from "@medusajs/medusa"
|
||||
import stream from "stream"
|
||||
import { resolve } from "path"
|
||||
import * as fs from "fs"
|
||||
import mkdirp from "mkdirp"
|
||||
|
||||
export default class LocalFileService extends AbstractFileService {
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
this.upload_dir_ =
|
||||
process.env.UPLOAD_DIR ?? options.upload_dir ?? "uploads/images"
|
||||
|
||||
if (!fs.existsSync(this.upload_dir_)) {
|
||||
fs.mkdirSync(this.upload_dir_)
|
||||
}
|
||||
}
|
||||
|
||||
upload(file) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, file.originalname)
|
||||
|
||||
let content = ""
|
||||
if (file.filename) {
|
||||
content = fs.readFileSync(
|
||||
resolve(process.cwd(), "uploads", file.filename)
|
||||
)
|
||||
}
|
||||
|
||||
const pathSegments = path.split("/")
|
||||
pathSegments.splice(-1)
|
||||
const dirname = pathSegments.join("/")
|
||||
mkdirp.sync(dirname, { recursive: true })
|
||||
|
||||
fs.writeFile(path, content.toString(), (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise({ url: path })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
delete({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
fs.unlink(path, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise("file unlinked")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getUploadStreamDescriptor({ name, ext }) {
|
||||
const fileKey = `${name}.${ext}`
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
|
||||
const isFileExists = fs.existsSync(path)
|
||||
if (!isFileExists) {
|
||||
await this.upload({ originalname: fileKey })
|
||||
}
|
||||
|
||||
const pass = new stream.PassThrough()
|
||||
pass.pipe(fs.createWriteStream(path))
|
||||
|
||||
return {
|
||||
writeStream: pass,
|
||||
promise: Promise.resolve(),
|
||||
url: `${this.upload_dir_}/${fileKey}`,
|
||||
fileKey,
|
||||
}
|
||||
}
|
||||
|
||||
async getDownloadStream({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
try {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
const data = fs.readFileSync(path)
|
||||
const readable = stream.Readable()
|
||||
readable._read = function () {}
|
||||
readable.push(data.toString())
|
||||
readable.push(null)
|
||||
resolvePromise(readable)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getPresignedDownloadUrl({ fileKey }) {
|
||||
return `${this.upload_dir_}/${fileKey}`
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class TestFulService extends FulfillmentService {
|
||||
static identifier = "test-ful"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "manual-fulfillment",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
validateFulfillmentData(data, cart) {
|
||||
return data
|
||||
}
|
||||
|
||||
validateOption(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
canCalculate() {
|
||||
return true
|
||||
}
|
||||
|
||||
calculatePrice(data) {
|
||||
return data.price
|
||||
}
|
||||
|
||||
createOrder() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createReturn() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createFulfillment() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
cancelFulfillment() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
|
||||
export default TestFulService
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NotificationService } from "medusa-interfaces"
|
||||
|
||||
class TestNotiService extends NotificationService {
|
||||
static identifier = "test-not"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async sendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
async resendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
export default TestNotiService
|
||||
@@ -1,87 +0,0 @@
|
||||
import { AbstractPaymentService } from "@medusajs/medusa"
|
||||
|
||||
class TestPayService extends AbstractPaymentService {
|
||||
static identifier = "test-pay"
|
||||
|
||||
constructor(_) {
|
||||
super(_)
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return []
|
||||
}
|
||||
|
||||
async createPayment(cart) {
|
||||
const fields = [
|
||||
"total",
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"shipping_total",
|
||||
"gift_card_total",
|
||||
]
|
||||
|
||||
const data = {}
|
||||
for (const k of fields) {
|
||||
data[k] = cart[k]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async createPaymentNew(inputData) {
|
||||
return inputData
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPaymentData(sessionData) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
if (
|
||||
sessionData.cart_id === "cart-id-tax-line-testing-for-pending-payment"
|
||||
) {
|
||||
return { data: {}, status: "pending" }
|
||||
}
|
||||
|
||||
return { data: {}, status: "authorized" }
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePayment(sessionData, cart) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePaymentNew(sessionData) {
|
||||
return sessionData
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(payment) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default TestPayService
|
||||
@@ -1,94 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const { DataSource } = require("typeorm")
|
||||
const { getConfigFile } = require("medusa-core-utils")
|
||||
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = process.env.DB_NAME
|
||||
const DB_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`
|
||||
|
||||
process.env.NODE_ENV = "development"
|
||||
|
||||
require("./dev-require")
|
||||
|
||||
async function createDB() {
|
||||
const connection = new DataSource({
|
||||
type: "postgres",
|
||||
url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}`,
|
||||
})
|
||||
|
||||
await connection.initialize()
|
||||
|
||||
await connection.query(`DROP DATABASE IF EXISTS "${DB_NAME}";`)
|
||||
await connection.query(`CREATE DATABASE "${DB_NAME}";`)
|
||||
await connection.destroy()
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initDb: async function () {
|
||||
const cwd = path.resolve(path.join(__dirname, "../.."))
|
||||
|
||||
const { configModule } = getConfigFile(
|
||||
path.join(__dirname),
|
||||
`medusa-config`
|
||||
)
|
||||
|
||||
const { featureFlags } = configModule
|
||||
|
||||
const basePath = path.join(cwd, "packages/medusa/src")
|
||||
|
||||
const featureFlagsLoader =
|
||||
require("@medusajs/medusa/dist/loaders/feature-flags").default
|
||||
|
||||
const featureFlagsRouter = featureFlagsLoader({ featureFlags })
|
||||
|
||||
const modelsLoader = require("@medusajs/medusa/dist/loaders/models").default
|
||||
|
||||
const {
|
||||
getEnabledMigrations,
|
||||
getModuleSharedResources,
|
||||
runIsolatedModulesMigration,
|
||||
} = require("@medusajs/medusa/dist/commands/utils/get-migrations")
|
||||
|
||||
const entities = modelsLoader({}, { register: false })
|
||||
|
||||
// get migraitons with enabled featureflags
|
||||
const migrationDir = path.resolve(
|
||||
path.join(basePath, `migrations`, `*.{j,t}s`)
|
||||
)
|
||||
|
||||
const isFlagEnabled = (flag) => featureFlagsRouter.isFeatureEnabled(flag)
|
||||
|
||||
const { migrations: moduleMigrations, models: moduleModels } =
|
||||
getModuleSharedResources(configModule, featureFlagsRouter)
|
||||
|
||||
const enabledMigrations = getEnabledMigrations(
|
||||
[migrationDir],
|
||||
isFlagEnabled
|
||||
)
|
||||
|
||||
const enabledEntities = entities.filter(
|
||||
(e) => typeof e.isFeatureEnabled === "undefined" || e.isFeatureEnabled()
|
||||
)
|
||||
|
||||
await createDB()
|
||||
|
||||
const dbConnection = new DataSource({
|
||||
type: "postgres",
|
||||
url: DB_URL,
|
||||
entities: enabledEntities.concat(moduleModels),
|
||||
migrations: enabledMigrations.concat(moduleMigrations),
|
||||
// logging: true,
|
||||
})
|
||||
|
||||
await dbConnection.initialize()
|
||||
|
||||
await dbConnection.runMigrations()
|
||||
|
||||
await runIsolatedModulesMigration(configModule)
|
||||
|
||||
return dbConnection
|
||||
},
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
import {
|
||||
DiscountCondition,
|
||||
DiscountConditionOperator,
|
||||
DiscountConditionType,
|
||||
} from "@medusajs/medusa/dist/models/discount-condition"
|
||||
import { DiscountConditionCustomerGroup } from "@medusajs/medusa/dist/models/discount-condition-customer-group"
|
||||
import { DiscountConditionProduct } from "@medusajs/medusa/dist/models/discount-condition-product"
|
||||
import { DiscountConditionProductCollection } from "@medusajs/medusa/dist/models/discount-condition-product-collection"
|
||||
import { DiscountConditionProductTag } from "@medusajs/medusa/dist/models/discount-condition-product-tag"
|
||||
import { DiscountConditionProductType } from "@medusajs/medusa/dist/models/discount-condition-product-type"
|
||||
import { DiscountConditionJoinTableForeignKey } from "@medusajs/medusa/dist/repositories/discount-condition"
|
||||
import faker from "faker"
|
||||
import { DataSource } from "typeorm"
|
||||
|
||||
export type DiscountConditionFactoryData = {
|
||||
id?: string
|
||||
rule_id: string
|
||||
type: DiscountConditionType
|
||||
operator: DiscountConditionOperator
|
||||
products: string[]
|
||||
product_collections: string[]
|
||||
product_types: string[]
|
||||
product_tags: string[]
|
||||
customer_groups: string[]
|
||||
}
|
||||
|
||||
const getJoinTableResourceIdentifiers = (type: string) => {
|
||||
let conditionTable: any
|
||||
let resourceKey
|
||||
|
||||
switch (type) {
|
||||
case DiscountConditionType.PRODUCTS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_ID
|
||||
conditionTable = DiscountConditionProduct
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TYPES: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_TYPE_ID
|
||||
conditionTable = DiscountConditionProductType
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_COLLECTIONS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_COLLECTION_ID
|
||||
conditionTable = DiscountConditionProductCollection
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.PRODUCT_TAGS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.PRODUCT_TAG_ID
|
||||
|
||||
conditionTable = DiscountConditionProductTag
|
||||
break
|
||||
}
|
||||
case DiscountConditionType.CUSTOMER_GROUPS: {
|
||||
resourceKey = DiscountConditionJoinTableForeignKey.CUSTOMER_GROUP_ID
|
||||
conditionTable = DiscountConditionCustomerGroup
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return {
|
||||
resourceKey,
|
||||
conditionTable,
|
||||
}
|
||||
}
|
||||
|
||||
export const simpleDiscountConditionFactory = async (
|
||||
dataSource: DataSource,
|
||||
data: DiscountConditionFactoryData,
|
||||
seed?: number
|
||||
): Promise<void> => {
|
||||
if (typeof seed !== "undefined") {
|
||||
faker.seed(seed)
|
||||
}
|
||||
|
||||
const manager = dataSource.manager
|
||||
|
||||
let resources = []
|
||||
|
||||
if (data.products) {
|
||||
resources = data.products
|
||||
}
|
||||
if (data.product_collections) {
|
||||
resources = data.product_collections
|
||||
}
|
||||
if (data.product_types) {
|
||||
resources = data.product_types
|
||||
}
|
||||
if (data.product_tags) {
|
||||
resources = data.product_tags
|
||||
}
|
||||
if (data.customer_groups) {
|
||||
resources = data.customer_groups
|
||||
}
|
||||
|
||||
const toCreate = {
|
||||
type: data.type,
|
||||
operator: data.operator,
|
||||
discount_rule_id: data.rule_id,
|
||||
}
|
||||
|
||||
if (data.id) {
|
||||
toCreate["id"] = data.id
|
||||
}
|
||||
|
||||
const condToSave = manager.create(DiscountCondition, toCreate)
|
||||
|
||||
const { conditionTable, resourceKey } = getJoinTableResourceIdentifiers(
|
||||
data.type
|
||||
)
|
||||
|
||||
const condition = await manager.save(condToSave)
|
||||
|
||||
for (const resourceCond of resources) {
|
||||
const toSave = manager.create(conditionTable, {
|
||||
[resourceKey]: resourceCond,
|
||||
condition_id: condition.id,
|
||||
})
|
||||
|
||||
await manager.save(toSave)
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,11 @@ import {
|
||||
} from "@medusajs/medusa"
|
||||
import faker from "faker"
|
||||
import { DataSource } from "typeorm"
|
||||
import {
|
||||
DiscountConditionFactoryData,
|
||||
simpleDiscountConditionFactory,
|
||||
} from "./simple-discount-condition-factory"
|
||||
|
||||
export type DiscountRuleFactoryData = {
|
||||
type?: DiscountRuleType
|
||||
value?: number
|
||||
allocation?: AllocationType
|
||||
conditions: DiscountConditionFactoryData[]
|
||||
}
|
||||
|
||||
export type DiscountFactoryData = {
|
||||
@@ -48,16 +43,6 @@ export const simpleDiscountFactory = async (
|
||||
|
||||
const dRule = await manager.save(ruleToSave)
|
||||
|
||||
if (data?.rule?.conditions) {
|
||||
for (const condition of data.rule.conditions) {
|
||||
await simpleDiscountConditionFactory(
|
||||
dataSource,
|
||||
{ ...condition, rule_id: dRule.id },
|
||||
1
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const toSave = manager.create(Discount, {
|
||||
id: data.id,
|
||||
is_dynamic: data.is_dynamic ?? false,
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const Scrypt = require("scrypt-kdf")
|
||||
const { User } = require("@medusajs/medusa/dist/models/user")
|
||||
|
||||
module.exports = async (dataSource, data = {}) => {
|
||||
const manager = dataSource.manager
|
||||
@@ -7,7 +6,7 @@ module.exports = async (dataSource, data = {}) => {
|
||||
const buf = await Scrypt.kdf("secret_password", { logN: 15, r: 8, p: 1 })
|
||||
const password_hash = buf.toString("base64")
|
||||
|
||||
const user = await manager.insert(User, {
|
||||
const user = await manager.insert("user", {
|
||||
id: "admin_user",
|
||||
email: "admin@medusa.js",
|
||||
api_token: "test_token",
|
||||
|
||||
@@ -118,7 +118,7 @@ medusaIntegrationTestRunner({
|
||||
price_set_id: priceSet.id,
|
||||
shipping_option_id: shippingOption.id,
|
||||
}),
|
||||
prices: [
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 5000,
|
||||
currency_code: "eur",
|
||||
@@ -127,7 +127,7 @@ medusaIntegrationTestRunner({
|
||||
amount: 3000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
],
|
||||
]),
|
||||
calculated_price: expect.objectContaining({
|
||||
calculated_amount: 5000,
|
||||
currency_code: "eur",
|
||||
|
||||
@@ -548,7 +548,8 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("POST /admin/price-lists/:id/prices/batch", () => {
|
||||
it("should add, remove and delete price list prices in batch successfully", async () => {
|
||||
// TODO: This is flaky, investigate why
|
||||
it.skip("should add, remove and delete price list prices in batch successfully", async () => {
|
||||
const priceSet = await createVariantPriceSet({
|
||||
container: appContainer,
|
||||
variantId: variant.id,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useApi } from "../../../../environment-helpers/use-api"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
|
||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||
import productSeeder from "../../../../helpers/product-seeder"
|
||||
|
||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||
import { MedusaV2Flag } from "@medusajs/utils"
|
||||
@@ -68,7 +67,6 @@ describe.skip("/admin/products", () => {
|
||||
|
||||
describe("POST /admin/products", () => {
|
||||
beforeEach(async () => {
|
||||
await productSeeder(dbConnection)
|
||||
await adminSeeder(dbConnection)
|
||||
await createDefaultRuleTypes(medusaContainer)
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { workflowEngineTestSuite } from "./tests"
|
||||
|
||||
jest.setTimeout(5000000)
|
||||
|
||||
const env = {
|
||||
MEDUSA_FF_MEDUSA_V2: false,
|
||||
}
|
||||
|
||||
workflowEngineTestSuite(env, { force_modules_migration: true })
|
||||
@@ -1,13 +0,0 @@
|
||||
let ignore = [`**/dist`]
|
||||
|
||||
// Jest needs to compile this code, but generally we don't want this copied
|
||||
// to output folders
|
||||
if (process.env.NODE_ENV !== `test`) {
|
||||
ignore.push(`**/__tests__`)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
sourceMaps: true,
|
||||
presets: ["babel-preset-medusa-package"],
|
||||
ignore,
|
||||
}
|
||||
4
integration-tests/plugins/.gitignore
vendored
4
integration-tests/plugins/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
dist/
|
||||
node_modules
|
||||
*yarn-error.log
|
||||
|
||||
@@ -1,96 +0,0 @@
|
||||
import { Region } from "@medusajs/medusa"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import { useApi } from "../../../../environment-helpers/use-api"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
import {
|
||||
simpleProductFactory,
|
||||
simpleSalesChannelFactory,
|
||||
} from "../../../../factories"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const env = {
|
||||
MEDUSA_FF_MEDUSA_V2: true,
|
||||
}
|
||||
|
||||
describe.skip("/store/carts", () => {
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
const doAfterEach = async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd, env } as any)
|
||||
shutdownServer = await startBootstrapApp({ cwd, env })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
describe("POST /store/carts", () => {
|
||||
let prod1
|
||||
let prodSale
|
||||
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await manager.insert(Region, {
|
||||
id: "region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
})
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country"
|
||||
SET region_id='region'
|
||||
WHERE iso_2 = 'us'`
|
||||
)
|
||||
|
||||
prod1 = await simpleProductFactory(dbConnection, {
|
||||
id: "test-product",
|
||||
variants: [{ id: "test-variant_1" }],
|
||||
})
|
||||
|
||||
prodSale = await simpleProductFactory(dbConnection, {
|
||||
id: "test-product-sale",
|
||||
variants: [
|
||||
{
|
||||
id: "test-variant-sale",
|
||||
prices: [{ amount: 1000, currency: "usd" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
await simpleSalesChannelFactory(dbConnection, {
|
||||
id: "amazon-sc",
|
||||
name: "Amazon store",
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await doAfterEach()
|
||||
})
|
||||
|
||||
it("should create a cart in a sales channel", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.post("/store/carts", {
|
||||
sales_channel_id: "amazon-sc",
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const getRes = await api.get(`/store/carts/${response.data.cart.id}`)
|
||||
expect(getRes.status).toEqual(200)
|
||||
expect(getRes.data.cart.sales_channel.id).toEqual("amazon-sc")
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,212 +0,0 @@
|
||||
import {
|
||||
MoneyAmount,
|
||||
PriceList,
|
||||
ProductVariantMoneyAmount,
|
||||
Region,
|
||||
} from "@medusajs/medusa"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import { useApi } from "../../../../environment-helpers/use-api"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
import { simpleProductFactory } from "../../../../factories"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("/store/carts", () => {
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
const doAfterEach = async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd } as any)
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
describe("POST /store/carts", () => {
|
||||
let prod1
|
||||
let prodSale
|
||||
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await manager.insert(Region, {
|
||||
id: "region",
|
||||
name: "Test Region",
|
||||
currency_code: "usd",
|
||||
tax_rate: 0,
|
||||
})
|
||||
|
||||
await manager.query(
|
||||
`UPDATE "country"
|
||||
SET region_id='region'
|
||||
WHERE iso_2 = 'us'`
|
||||
)
|
||||
|
||||
prod1 = await simpleProductFactory(dbConnection, {
|
||||
id: "test-product",
|
||||
variants: [{ id: "test-variant_1" }],
|
||||
})
|
||||
|
||||
prodSale = await simpleProductFactory(dbConnection, {
|
||||
id: "test-product-sale",
|
||||
variants: [
|
||||
{
|
||||
id: "test-variant-sale",
|
||||
prices: [{ amount: 1000, currency: "usd" }],
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await doAfterEach()
|
||||
})
|
||||
|
||||
it("should create a cart", async () => {
|
||||
const api = useApi()
|
||||
const response = await api.post("/store/carts")
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
|
||||
expect(getRes.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("should fail to create a cart when no region exist", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await dbConnection.manager.query(
|
||||
`UPDATE "country"
|
||||
SET region_id=null
|
||||
WHERE iso_2 = 'us'`
|
||||
)
|
||||
|
||||
await dbConnection.manager.query(`DELETE from region`)
|
||||
|
||||
try {
|
||||
await api.post("/store/carts")
|
||||
} catch (error) {
|
||||
expect(error.response.status).toEqual(400)
|
||||
expect(error.response.data.message).toEqual(
|
||||
"A region is required to create a cart"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("should create a cart with items", async () => {
|
||||
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 priceList1 = await dbConnection.manager.create(PriceList, {
|
||||
id: "pl_current",
|
||||
name: "Past winter sale",
|
||||
description: "Winter sale for key accounts.",
|
||||
type: "sale",
|
||||
status: "active",
|
||||
starts_at: yesterday,
|
||||
ends_at: tomorrow,
|
||||
})
|
||||
|
||||
await dbConnection.manager.save(priceList1)
|
||||
|
||||
const ma_sale_1 = dbConnection.manager.create(MoneyAmount, {
|
||||
currency_code: "usd",
|
||||
amount: 800,
|
||||
price_list_id: "pl_current",
|
||||
})
|
||||
|
||||
await dbConnection.manager.save(ma_sale_1)
|
||||
|
||||
await dbConnection.manager.insert(ProductVariantMoneyAmount, {
|
||||
id: "pvma-test",
|
||||
variant_id: prodSale.variants[0].id,
|
||||
money_amount_id: ma_sale_1.id,
|
||||
})
|
||||
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.post("/store/carts", {
|
||||
items: [
|
||||
{
|
||||
variant_id: prod1.variants[0].id,
|
||||
quantity: 1,
|
||||
},
|
||||
{
|
||||
variant_id: prodSale.variants[0].id,
|
||||
quantity: 2,
|
||||
},
|
||||
],
|
||||
})
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
response.data.cart.items.sort((a, b) => a.quantity - b.quantity)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart.items).toHaveLength(2)
|
||||
expect(response.data.cart.items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
variant_id: prod1.variants[0].id,
|
||||
quantity: 1,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
variant_id: prodSale.variants[0].id,
|
||||
quantity: 2,
|
||||
unit_price: 800,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
|
||||
expect(getRes.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("should create a cart with country", async () => {
|
||||
const api = useApi()
|
||||
const response = await api.post("/store/carts", {
|
||||
country_code: "us",
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.cart.shipping_address.country_code).toEqual("us")
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
|
||||
expect(getRes.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("should create a cart with context", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.post("/store/carts", {
|
||||
context: {
|
||||
test_id: "test",
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const getRes = await api.post(`/store/carts/${response.data.cart.id}`)
|
||||
expect(getRes.status).toEqual(200)
|
||||
|
||||
const cart = getRes.data.cart
|
||||
expect(cart.context).toEqual({
|
||||
ip: expect.any(String),
|
||||
user_agent: expect.stringContaining("axios/0.21."),
|
||||
test_id: "test",
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,362 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
const cartSeeder = require("../../../../helpers/cart-seeder")
|
||||
const { simpleProductFactory } = require("../../../../factories")
|
||||
const { simpleSalesChannelFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
jest.setTimeout(60000)
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("/store/carts", () => {
|
||||
let shutdownServer
|
||||
let appContainer
|
||||
let dbConnection
|
||||
|
||||
let variantId
|
||||
let inventoryItemId
|
||||
let locationId
|
||||
|
||||
const doAfterEach = async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("POST /store/carts/:id", () => {
|
||||
beforeEach(async () => {
|
||||
await simpleSalesChannelFactory(dbConnection, {
|
||||
id: "test-channel",
|
||||
is_default: true,
|
||||
})
|
||||
|
||||
await adminSeeder(dbConnection)
|
||||
await cartSeeder(dbConnection, { sales_channel_id: "test-channel" })
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "product1",
|
||||
sales_channels: [{ id: "test-channel" }],
|
||||
variants: [],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
// Add payment provider
|
||||
await api.post(
|
||||
`/admin/regions/test-region/payment-providers`,
|
||||
{
|
||||
provider_id: "test-pay",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const prodVarInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/product1/variants`,
|
||||
{
|
||||
title: "Test Variant w. inventory",
|
||||
sku: "MY_SKU",
|
||||
material: "material",
|
||||
origin_country: "UK",
|
||||
hs_code: "hs001",
|
||||
mid_code: "mids",
|
||||
weight: 300,
|
||||
length: 100,
|
||||
height: 200,
|
||||
width: 150,
|
||||
options: [
|
||||
{
|
||||
option_id: "product1-option",
|
||||
value: "SS",
|
||||
},
|
||||
],
|
||||
manage_inventory: true,
|
||||
prices: [{ currency_code: "usd", amount: 2300 }],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const variant = response.data.product.variants[0]
|
||||
|
||||
variantId = variant.id
|
||||
|
||||
const inventoryItems =
|
||||
await prodVarInventoryService.listInventoryItemsByVariant(variantId)
|
||||
|
||||
inventoryItemId = inventoryItems[0].id
|
||||
|
||||
// Add Stock location
|
||||
const stockRes = await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{
|
||||
name: "Fake Warehouse",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
locationId = stockRes.data.stock_location.id
|
||||
|
||||
// Add stock level
|
||||
await api.post(
|
||||
`/admin/inventory-items/${inventoryItemId}/location-levels`,
|
||||
{
|
||||
location_id: locationId,
|
||||
stocked_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
// Associate Stock Location with sales channel
|
||||
await api.post(
|
||||
`/admin/sales-channels/test-channel/stock-locations`,
|
||||
{
|
||||
location_id: locationId,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await doAfterEach()
|
||||
})
|
||||
|
||||
it("reserve quantity when completing the cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartId = "test-cart"
|
||||
|
||||
// Add standard line item to cart
|
||||
await api.post(
|
||||
`/store/carts/${cartId}/line-items`,
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
await api.post(`/store/carts/${cartId}/payment-sessions`)
|
||||
await api.post(`/store/carts/${cartId}/payment-session`, {
|
||||
provider_id: "test-pay",
|
||||
})
|
||||
|
||||
const getRes = await api.post(`/store/carts/${cartId}/complete`)
|
||||
|
||||
expect(getRes.status).toEqual(200)
|
||||
expect(getRes.data.type).toEqual("order")
|
||||
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const stockLevel = await inventoryService.retrieveInventoryLevel(
|
||||
inventoryItemId,
|
||||
locationId
|
||||
)
|
||||
|
||||
expect(stockLevel.location_id).toEqual(locationId)
|
||||
expect(stockLevel.inventory_item_id).toEqual(inventoryItemId)
|
||||
expect(stockLevel.reserved_quantity).toEqual(3)
|
||||
expect(stockLevel.stocked_quantity).toEqual(5)
|
||||
})
|
||||
|
||||
it("removes reserved quantity when failing to complete the cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartRes = await api.post(
|
||||
`/store/carts`,
|
||||
{
|
||||
region_id: "test-region",
|
||||
items: [
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 3,
|
||||
},
|
||||
],
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
const cartId = cartRes.data.cart.id
|
||||
|
||||
await api.post(`/store/carts/${cartId}/payment-sessions`)
|
||||
await api.post(`/store/carts/${cartId}/payment-session`, {
|
||||
provider_id: "test-pay",
|
||||
})
|
||||
|
||||
const getRes = await api
|
||||
.post(`/store/carts/${cartId}/complete`)
|
||||
.catch((err) => err)
|
||||
|
||||
expect(getRes.response.status).toEqual(400)
|
||||
expect(getRes.response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message: "Cannot create an order from the cart without a customer",
|
||||
})
|
||||
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const [, count] = await inventoryService.listReservationItems({
|
||||
line_item_id: cartRes.data.cart.items.map((i) => i.id),
|
||||
})
|
||||
expect(count).toEqual(0)
|
||||
})
|
||||
|
||||
it("should decorate line item variant inventory_quantity when creating a line-item", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartId = "test-cart"
|
||||
|
||||
// Add standard line item to cart
|
||||
const addCart = await api
|
||||
.post(
|
||||
`/store/carts/${cartId}/line-items`,
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(addCart.status).toEqual(200)
|
||||
expect(addCart.data.cart.items[0].variant.inventory_quantity).toEqual(5)
|
||||
})
|
||||
|
||||
it("should decorate line item variant inventory_quantity when getting cart", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartId = "test-cart"
|
||||
|
||||
// Add standard line item to cart
|
||||
await api
|
||||
.post(
|
||||
`/store/carts/${cartId}/line-items`,
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 3,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
const cartResponse = await api
|
||||
.get(`/store/carts/${cartId}`, { withCredentials: true })
|
||||
.catch((e) => e)
|
||||
|
||||
expect(cartResponse.status).toEqual(200)
|
||||
expect(
|
||||
cartResponse.data.cart.items[0].variant.inventory_quantity
|
||||
).toEqual(5)
|
||||
})
|
||||
|
||||
it("fails to add a item on the cart if the inventory isn't enough", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartId = "test-cart"
|
||||
|
||||
// Add standard line item to cart
|
||||
const addCart = await api
|
||||
.post(
|
||||
`/store/carts/${cartId}/line-items`,
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 6,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(addCart.response.status).toEqual(400)
|
||||
expect(addCart.response.data.code).toEqual("insufficient_inventory")
|
||||
expect(addCart.response.data.message).toEqual(
|
||||
`Variant with id: ${variantId} does not have the required inventory`
|
||||
)
|
||||
})
|
||||
|
||||
it("fails to complete cart with items inventory not covered", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const cartId = "test-cart"
|
||||
|
||||
// Add standard line item to cart
|
||||
await api.post(
|
||||
`/store/carts/${cartId}/line-items`,
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 5,
|
||||
},
|
||||
{ withCredentials: true }
|
||||
)
|
||||
|
||||
await api.post(`/store/carts/${cartId}/payment-sessions`)
|
||||
await api.post(`/store/carts/${cartId}/payment-session`, {
|
||||
provider_id: "test-pay",
|
||||
})
|
||||
|
||||
// Another proccess reserves items before the cart is completed
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
inventoryService.createReservationItem({
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItemId,
|
||||
location_id: locationId,
|
||||
quantity: 2,
|
||||
})
|
||||
|
||||
const completeCartRes = await api
|
||||
.post(`/store/carts/${cartId}/complete`)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(completeCartRes.response.status).toEqual(409)
|
||||
expect(completeCartRes.response.data.errors[0].code).toEqual(
|
||||
"insufficient_inventory"
|
||||
)
|
||||
expect(completeCartRes.response.data.errors[0].message).toEqual(
|
||||
`Variant with id: ${variantId} does not have the required inventory`
|
||||
)
|
||||
|
||||
const stockLevel = await inventoryService.retrieveInventoryLevel(
|
||||
inventoryItemId,
|
||||
locationId
|
||||
)
|
||||
|
||||
expect(stockLevel.location_id).toEqual(locationId)
|
||||
expect(stockLevel.inventory_item_id).toEqual(inventoryItemId)
|
||||
expect(stockLevel.reserved_quantity).toEqual(2)
|
||||
expect(stockLevel.stocked_quantity).toEqual(5)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,86 +0,0 @@
|
||||
const path = require("path")
|
||||
const { ProductVariantInventoryService } = require("@medusajs/medusa")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Inventory Items endpoints", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
|
||||
// Set feature flag
|
||||
const flagRouter = appContainer.resolve("featureFlagRouter")
|
||||
flagRouter.setFlag("many_to_many_inventory", true)
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const flagRouter = appContainer.resolve("featureFlagRouter")
|
||||
flagRouter.setFlag("many_to_many_inventory", false)
|
||||
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Items", () => {
|
||||
it("should create inventory item without variant id", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{
|
||||
sku: "TABLE_LEG",
|
||||
description: "Table Leg",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
/** @type {ProductVariantInventoryService} */
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const inventoryItems = await inventoryService.list()
|
||||
|
||||
expect(inventoryItems.length).toEqual(1)
|
||||
|
||||
const variants = await productVariantInventoryService.listByItem([
|
||||
inventoryItems[0].id,
|
||||
])
|
||||
expect(variants.length).toEqual(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,180 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleCustomerFactory,
|
||||
} = require("../../../../factories")
|
||||
const {
|
||||
simpleRegionFactory,
|
||||
simpleShippingOptionFactory,
|
||||
} = require("../../../../factories")
|
||||
const {
|
||||
simpleAddressFactory,
|
||||
} = require("../../../../factories/simple-address-factory")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("/store/carts", () => {
|
||||
let shutdownServer
|
||||
let appContainer
|
||||
let dbConnection
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
beforeEach(async () => {})
|
||||
|
||||
describe("POST /store/carts", () => {
|
||||
const variantId = "test-variant"
|
||||
|
||||
let region
|
||||
let invItemId
|
||||
let prodVarInventoryService
|
||||
let inventoryService
|
||||
let lineItemService
|
||||
let stockLocationService
|
||||
let salesChannelLocationService
|
||||
|
||||
let address
|
||||
let shippingOption
|
||||
let customer
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
const api = useApi()
|
||||
|
||||
prodVarInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
lineItemService = appContainer.resolve("lineItemService")
|
||||
inventoryService = appContainer.resolve("inventoryService")
|
||||
stockLocationService = appContainer.resolve("stockLocationService")
|
||||
salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
// create region
|
||||
region = await simpleRegionFactory(dbConnection, {})
|
||||
|
||||
// create product
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
variants: [{ id: variantId }],
|
||||
})
|
||||
|
||||
const location = await stockLocationService.create({
|
||||
name: "test-location",
|
||||
})
|
||||
|
||||
const invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku",
|
||||
})
|
||||
invItemId = invItem.id
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: location.id,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
|
||||
await prodVarInventoryService.attachInventoryItem(variantId, invItem.id)
|
||||
|
||||
// create customer
|
||||
customer = await simpleCustomerFactory(dbConnection, {})
|
||||
|
||||
address = await simpleAddressFactory(dbConnection, {})
|
||||
|
||||
// create shipping option
|
||||
shippingOption = await simpleShippingOptionFactory(dbConnection, {
|
||||
region_id: region.id,
|
||||
})
|
||||
})
|
||||
|
||||
it("should create the order from a draft order and shouldn't adjust reservations", async () => {
|
||||
const api = useApi()
|
||||
let inventoryItem = await api.get(
|
||||
`/admin/inventory-items/${invItemId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(inventoryItem.data.inventory_item.location_levels.length).toEqual(
|
||||
1
|
||||
)
|
||||
let locationLevel = inventoryItem.data.inventory_item.location_levels[0]
|
||||
|
||||
expect(locationLevel.stocked_quantity).toEqual(10)
|
||||
expect(locationLevel.reserved_quantity).toEqual(0)
|
||||
|
||||
const payload = {
|
||||
email: "test@test.dk",
|
||||
shipping_address: address.id,
|
||||
discounts: [],
|
||||
items: [
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 2,
|
||||
metadata: {},
|
||||
},
|
||||
],
|
||||
region_id: region.id,
|
||||
customer_id: customer.id,
|
||||
shipping_methods: [
|
||||
{
|
||||
option_id: shippingOption.id,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const createResponse = await api.post(
|
||||
"/admin/draft-orders",
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
expect(createResponse.status).toEqual(200)
|
||||
|
||||
const registerPaymentResponse = await api.post(
|
||||
`/admin/draft-orders/${createResponse.data.draft_order.id}/pay`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
expect(registerPaymentResponse.status).toEqual(200)
|
||||
|
||||
inventoryItem = await api.get(
|
||||
`/admin/inventory-items/${invItemId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(inventoryItem.data.inventory_item.location_levels.length).toEqual(
|
||||
1
|
||||
)
|
||||
locationLevel = inventoryItem.data.inventory_item.location_levels[0]
|
||||
|
||||
expect(locationLevel.stocked_quantity).toEqual(10)
|
||||
expect(locationLevel.reserved_quantity).toEqual(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,176 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const {
|
||||
ProductVariantInventoryService,
|
||||
ProductVariantService,
|
||||
} = require("@medusajs/medusa")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const { simpleProductFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
describe("Create Variant", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Items", () => {
|
||||
it("When creating a new product variant it should create an Inventory Item", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "test-product",
|
||||
variants: [{ id: "test-variant" }],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/test-product/variants`,
|
||||
{
|
||||
title: "Test Variant w. inventory",
|
||||
sku: "MY_SKU",
|
||||
material: "material",
|
||||
origin_country: "UK",
|
||||
hs_code: "hs001",
|
||||
mid_code: "mids",
|
||||
weight: 300,
|
||||
length: 100,
|
||||
height: 200,
|
||||
width: 150,
|
||||
manage_inventory: true,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-product-option",
|
||||
value: "SS",
|
||||
},
|
||||
],
|
||||
prices: [{ currency_code: "usd", amount: 2300 }],
|
||||
},
|
||||
{ headers: { "x-medusa-access-token": "test_token" } }
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const variantId = response.data.product.variants.find(
|
||||
(v) => v.id !== "test-variant"
|
||||
).id
|
||||
|
||||
const variantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
const inventory =
|
||||
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
||||
|
||||
expect(inventory).toHaveLength(1)
|
||||
expect(inventory).toEqual([
|
||||
expect.objectContaining({
|
||||
origin_country: "UK",
|
||||
hs_code: "hs001",
|
||||
mid_code: "mids",
|
||||
weight: 300,
|
||||
length: 100,
|
||||
height: 200,
|
||||
width: 150,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("When creating a new variant fails, it should revert all the transaction", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "test-product",
|
||||
variants: [{ id: "test-variant" }],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
jest
|
||||
.spyOn(ProductVariantInventoryService.prototype, "attachInventoryItem")
|
||||
.mockImplementation(() => {
|
||||
throw new Error("Failure while attaching inventory item")
|
||||
})
|
||||
|
||||
const prodVariantDeleteMock = jest.spyOn(
|
||||
ProductVariantService.prototype,
|
||||
"delete"
|
||||
)
|
||||
|
||||
const error = await api
|
||||
.post(
|
||||
`/admin/products/test-product/variants`,
|
||||
{
|
||||
title: "Test Variant w. inventory",
|
||||
sku: "MY_SKU",
|
||||
material: "material",
|
||||
origin_country: "UK",
|
||||
hs_code: "hs001",
|
||||
mid_code: "mids",
|
||||
weight: 300,
|
||||
length: 100,
|
||||
height: 200,
|
||||
width: 150,
|
||||
manage_inventory: true,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-product-option",
|
||||
value: "SS",
|
||||
},
|
||||
],
|
||||
prices: [{ currency_code: "usd", amount: 2300 }],
|
||||
},
|
||||
{ headers: { "x-medusa-access-token": "test_token" } }
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(error.response.status).toEqual(400)
|
||||
expect(error.response.data.message).toEqual(
|
||||
"Failure while attaching inventory item"
|
||||
)
|
||||
|
||||
expect(prodVariantDeleteMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,114 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const { simpleProductFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
describe("Delete Variant", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Items", () => {
|
||||
it("When deleting a product variant it removes the inventory items associated to it", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const api = useApi()
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: "test-product",
|
||||
variants: [{ id: "test-variant" }],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/test-product/variants`,
|
||||
{
|
||||
title: "Test Variant w. inventory",
|
||||
sku: "MY_SKU",
|
||||
manage_inventory: true,
|
||||
options: [
|
||||
{
|
||||
option_id: "test-product-option",
|
||||
value: "SS",
|
||||
},
|
||||
],
|
||||
prices: [{ currency_code: "usd", amount: 2300 }],
|
||||
},
|
||||
{ headers: { "x-medusa-access-token": "test_token" } }
|
||||
)
|
||||
|
||||
const variantId = response.data.product.variants.find(
|
||||
(v) => v.sku === "MY_SKU"
|
||||
).id
|
||||
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const variantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
const variantService = appContainer.resolve("productVariantService")
|
||||
|
||||
const invItem2 = await inventoryService.createInventoryItem({
|
||||
sku: "123456",
|
||||
})
|
||||
|
||||
await variantInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
invItem2.id,
|
||||
2
|
||||
)
|
||||
|
||||
expect(
|
||||
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
||||
).toHaveLength(2)
|
||||
|
||||
await api.delete(`/admin/products/test-product/variants/${variantId}`, {
|
||||
headers: { "x-medusa-access-token": "test_token" },
|
||||
})
|
||||
|
||||
await expect(variantService.retrieve(variantId)).rejects.toThrow(
|
||||
`Variant with id: ${variantId} was not found`
|
||||
)
|
||||
|
||||
expect(
|
||||
await variantInventoryService.listInventoryItemsByVariant(variantId)
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,151 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleSalesChannelFactory,
|
||||
} = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Get products", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
const productId = "test-product"
|
||||
const variantId = "test-variant"
|
||||
let invItem
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const locationService = appContainer.resolve("stockLocationService")
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const salesChannel = await simpleSalesChannelFactory(dbConnection, {
|
||||
is_default: true,
|
||||
})
|
||||
|
||||
const location = await locationService.create({ name: "test-location" })
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: productId,
|
||||
status: "published",
|
||||
variants: [{ id: variantId }],
|
||||
},
|
||||
100
|
||||
)
|
||||
await salesChannelService.addProducts(salesChannel.id, [productId])
|
||||
await salesChannelLocationService.associateLocation(
|
||||
salesChannel.id,
|
||||
location.id
|
||||
)
|
||||
invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku",
|
||||
})
|
||||
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
invItem.id
|
||||
)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: location.id,
|
||||
stocked_quantity: 100,
|
||||
})
|
||||
})
|
||||
|
||||
describe("/store/products/:id", () => {
|
||||
it("Expands inventory items when getting product with expand parameters", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(
|
||||
`/store/products/${productId}?expand=variants,variants.inventory_items`
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
id: productId,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
id: variantId,
|
||||
inventory_items: [
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem.id,
|
||||
variant_id: variantId,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("/admin/products/:id", () => {
|
||||
it("should get inventory quantity for products fetched through the admin api", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(`/admin/products/${productId}`, adminHeaders)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
id: productId,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
id: variantId,
|
||||
inventory_quantity: 100,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,144 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleSalesChannelFactory,
|
||||
} = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Get variant", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
const productId = "test-product"
|
||||
const variantId = "test-variant"
|
||||
let invItem
|
||||
let salesChannelService
|
||||
let salesChannelLocationService
|
||||
let location
|
||||
let inventoryService
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
inventoryService = appContainer.resolve("inventoryService")
|
||||
salesChannelService = appContainer.resolve("salesChannelService")
|
||||
salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
|
||||
location = await stockLocationService.create({
|
||||
name: "test-location",
|
||||
})
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: productId,
|
||||
status: "published",
|
||||
variants: [{ id: variantId }],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku",
|
||||
})
|
||||
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
invItem.id
|
||||
)
|
||||
})
|
||||
|
||||
it("Expands inventory items when getting variant with expand parameters", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(
|
||||
`/store/variants/${variantId}?expand=inventory_items`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variant).toEqual(
|
||||
expect.objectContaining({
|
||||
id: variantId,
|
||||
inventory_items: [
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem.id,
|
||||
variant_id: variantId,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("sets availability correctly", async () => {
|
||||
const salesChannel = await simpleSalesChannelFactory(dbConnection, {
|
||||
is_default: true,
|
||||
})
|
||||
|
||||
await salesChannelService.addProducts(salesChannel.id, [productId])
|
||||
|
||||
await salesChannelLocationService.associateLocation(
|
||||
salesChannel.id,
|
||||
location.id
|
||||
)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: location.id,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(`/store/variants/${variantId}`)
|
||||
|
||||
expect(response.data).toEqual({
|
||||
variant: expect.objectContaining({
|
||||
purchasable: true,
|
||||
inventory_quantity: 10,
|
||||
}),
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,348 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const { simpleProductFactory } = require("../../../../factories")
|
||||
const { simpleSalesChannelFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Create Variant", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("list-products", () => {
|
||||
const productId = "test-product"
|
||||
const variantId = "test-variant"
|
||||
let sc2
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: productId,
|
||||
status: "published",
|
||||
sales_channels: [],
|
||||
variants: [{ id: variantId }],
|
||||
},
|
||||
100
|
||||
)
|
||||
|
||||
const sc1 = await simpleSalesChannelFactory(dbConnection, {})
|
||||
sc2 = await simpleSalesChannelFactory(dbConnection, {})
|
||||
const sc3 = await simpleSalesChannelFactory(dbConnection, {})
|
||||
|
||||
const sl1 = await stockLocationService.create({ name: "sl1" })
|
||||
const sl2 = await stockLocationService.create({ name: "sl2" })
|
||||
|
||||
await salesChannelLocationService.associateLocation(sc1.id, sl1.id)
|
||||
await salesChannelLocationService.associateLocation(sc2.id, sl1.id)
|
||||
await salesChannelLocationService.associateLocation(sc2.id, sl2.id)
|
||||
|
||||
const invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku",
|
||||
})
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
invItem.id
|
||||
)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: sl1.id,
|
||||
stocked_quantity: 3,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: sl2.id,
|
||||
stocked_quantity: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it("lists location availability correctly", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(`/admin/products`, adminHeaders)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.products).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productId,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
inventory_quantity: 4,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
describe("/store/products", () => {
|
||||
beforeEach(async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const productVariantInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: `${productId}-1`,
|
||||
status: "published",
|
||||
sales_channels: [],
|
||||
variants: [
|
||||
{
|
||||
id: `${variantId}-1`,
|
||||
manage_inventory: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
101
|
||||
)
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: `${productId}-2`,
|
||||
status: "published",
|
||||
variants: [{ id: `${variantId}-2`, manage_inventory: true }],
|
||||
sales_channels: [],
|
||||
},
|
||||
102
|
||||
)
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: `${productId}-3`,
|
||||
status: "published",
|
||||
sales_channels: [],
|
||||
variants: [
|
||||
{
|
||||
id: `${variantId}-3`,
|
||||
manage_inventory: true,
|
||||
allow_backorder: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
103
|
||||
)
|
||||
const invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku-1",
|
||||
})
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
`${variantId}-3`,
|
||||
invItem.id
|
||||
)
|
||||
await simpleProductFactory(
|
||||
dbConnection,
|
||||
{
|
||||
id: `${productId}-4`,
|
||||
status: "published",
|
||||
sales_channels: [],
|
||||
variants: [
|
||||
{
|
||||
id: `${variantId}-4`,
|
||||
manage_inventory: true,
|
||||
allow_backorder: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
104
|
||||
)
|
||||
const invItem1 = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku-2",
|
||||
})
|
||||
await productVariantInventoryService.attachInventoryItem(
|
||||
`${variantId}-4`,
|
||||
invItem1.id
|
||||
)
|
||||
})
|
||||
|
||||
it("includes inventory items when property is expanded", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const result = await api.get(
|
||||
`/store/products?expand=variants,variants.inventory_items`
|
||||
)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_items: [expect.any(Object)],
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("lists location availability correctly for store", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(`/store/products`)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.products).toEqual([
|
||||
expect.objectContaining({
|
||||
id: `${productId}-4`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-3`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-2`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-1`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
inventory_quantity: 10,
|
||||
purchasable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productId,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("lists location availability correctly for store with sales channel id", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const productService = appContainer.resolve("productService")
|
||||
|
||||
const ids = [
|
||||
`${productId}`,
|
||||
`${productId}-1`,
|
||||
`${productId}-2`,
|
||||
`${productId}-3`,
|
||||
`${productId}-4`,
|
||||
]
|
||||
|
||||
for (const id of ids) {
|
||||
await productService.update(id, {
|
||||
sales_channels: [{ id: sc2.id }],
|
||||
})
|
||||
}
|
||||
|
||||
const res = await api.get(
|
||||
`/store/products?sales_channel_id[]=${sc2.id}`
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.products).toEqual([
|
||||
expect.objectContaining({
|
||||
id: `${productId}-4`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: false,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-3`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-2`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: `${productId}-1`,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
inventory_quantity: 10,
|
||||
purchasable: true,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productId,
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: true,
|
||||
inventory_quantity: 4,
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,147 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const { simpleProductFactory } = require("../../../../factories")
|
||||
const { simpleSalesChannelFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("List Variants", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Items", () => {
|
||||
const variantId = "test-variant"
|
||||
let invItem
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
const prodVarInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
const location = await stockLocationService.create({
|
||||
name: "test-location",
|
||||
})
|
||||
|
||||
const salesChannel = await simpleSalesChannelFactory(dbConnection, {
|
||||
is_default: true,
|
||||
})
|
||||
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
variants: [{ id: variantId }],
|
||||
})
|
||||
|
||||
await salesChannelService.addProducts(salesChannel.id, [product.id])
|
||||
await salesChannelLocationService.associateLocation(
|
||||
salesChannel.id,
|
||||
location.id
|
||||
)
|
||||
|
||||
invItem = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku",
|
||||
})
|
||||
|
||||
const invItemId = invItem.id
|
||||
|
||||
await prodVarInventoryService.attachInventoryItem(variantId, invItem.id)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: invItem.id,
|
||||
location_id: location.id,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
})
|
||||
|
||||
it("Decorates inventory quantities when listing variants", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const listVariantsRes = await api.get(`/admin/variants`, adminHeaders)
|
||||
|
||||
expect(listVariantsRes.status).toEqual(200)
|
||||
expect(listVariantsRes.data.variants.length).toEqual(1)
|
||||
expect(listVariantsRes.data.variants[0]).toEqual(
|
||||
expect.objectContaining({ id: variantId, inventory_quantity: 10 })
|
||||
)
|
||||
})
|
||||
|
||||
it("expands inventory_items when querying with expand parameter", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const listVariantsRes = await api.get(
|
||||
`/admin/variants?expand=inventory_items`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(listVariantsRes.status).toEqual(200)
|
||||
expect(listVariantsRes.data.variants.length).toEqual(1)
|
||||
expect(listVariantsRes.data.variants[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: variantId,
|
||||
inventory_items: [
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem.id,
|
||||
variant_id: variantId,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("sets availability correctly", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(`/store/variants?ids=${variantId}`)
|
||||
|
||||
expect(response.data).toEqual({
|
||||
variants: [
|
||||
expect.objectContaining({
|
||||
purchasable: true,
|
||||
inventory_quantity: 10,
|
||||
}),
|
||||
],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,459 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const {
|
||||
simpleProductFactory,
|
||||
simpleOrderFactory,
|
||||
simpleRegionFactory,
|
||||
} = require("../../../../factories")
|
||||
const { simpleSalesChannelFactory } = require("../../../../factories")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Inventory Items endpoints", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
let inventoryItem
|
||||
let locationId
|
||||
|
||||
let prodVarInventoryService
|
||||
let inventoryService
|
||||
let stockLocationService
|
||||
let salesChannelLocationService
|
||||
|
||||
let reg
|
||||
let regionId
|
||||
let order
|
||||
let variantId
|
||||
let reservationItem
|
||||
let lineItemId
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
const api = useApi()
|
||||
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
prodVarInventoryService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
inventoryService = appContainer.resolve("inventoryService")
|
||||
stockLocationService = appContainer.resolve("stockLocationService")
|
||||
salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const r = await simpleRegionFactory(dbConnection, {})
|
||||
regionId = r.id
|
||||
await simpleSalesChannelFactory(dbConnection, {
|
||||
id: "test-channel",
|
||||
is_default: true,
|
||||
})
|
||||
|
||||
await simpleProductFactory(dbConnection, {
|
||||
id: "product1",
|
||||
sales_channels: [{ id: "test-channel" }],
|
||||
})
|
||||
|
||||
const productRes = await api.get(`/admin/products/product1`, adminHeaders)
|
||||
|
||||
variantId = productRes.data.product.variants[0].id
|
||||
|
||||
const stockRes = await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{
|
||||
name: "Fake Warehouse",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
locationId = stockRes.data.stock_location.id
|
||||
|
||||
await salesChannelLocationService.associateLocation(
|
||||
"test-channel",
|
||||
locationId
|
||||
)
|
||||
|
||||
inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "1234",
|
||||
})
|
||||
|
||||
await prodVarInventoryService.attachInventoryItem(
|
||||
variantId,
|
||||
inventoryItem.id
|
||||
)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 100,
|
||||
})
|
||||
|
||||
order = await simpleOrderFactory(dbConnection, {
|
||||
sales_channel: "test-channel",
|
||||
line_items: [
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 2,
|
||||
id: "line-item-id",
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: {
|
||||
region_id: r.id,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
const orderRes = await api.get(`/admin/orders/${order.id}`, adminHeaders)
|
||||
|
||||
lineItemId = orderRes.data.order.items[0].id
|
||||
|
||||
reservationItem = await inventoryService.createReservationItem({
|
||||
line_item_id: lineItemId,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 2,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Reservation items", () => {
|
||||
it("Create reservation item throws if available item quantity is less than reservation quantity", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const orderRes = await api.get(`/admin/orders/${order.id}`, adminHeaders)
|
||||
|
||||
expect(orderRes.data.order.items[0].quantity).toBe(2)
|
||||
expect(orderRes.data.order.items[0].fulfilled_quantity).toBeFalsy()
|
||||
|
||||
const payload = {
|
||||
quantity: 1,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
line_item_id: lineItemId,
|
||||
location_id: locationId,
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post(`/admin/reservations`, payload, adminHeaders)
|
||||
.catch((err) => err)
|
||||
|
||||
expect(res.response.status).toBe(400)
|
||||
expect(res.response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message:
|
||||
"The reservation quantity cannot be greater than the unfulfilled line item quantity",
|
||||
})
|
||||
})
|
||||
|
||||
it("Update reservation item throws if available item quantity is less than reservation quantity", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const orderRes = await api.get(`/admin/orders/${order.id}`, adminHeaders)
|
||||
|
||||
expect(orderRes.data.order.items[0].quantity).toBe(2)
|
||||
expect(orderRes.data.order.items[0].fulfilled_quantity).toBeFalsy()
|
||||
|
||||
const payload = {
|
||||
quantity: 3,
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post(
|
||||
`/admin/reservations/${reservationItem.id}`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => err)
|
||||
|
||||
expect(res.response.status).toBe(400)
|
||||
expect(res.response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message:
|
||||
"The reservation quantity cannot be greater than the unfulfilled line item quantity",
|
||||
})
|
||||
})
|
||||
|
||||
describe("List reservation items", () => {
|
||||
let item2
|
||||
let location2
|
||||
let reservation2
|
||||
|
||||
beforeEach(async () => {
|
||||
const api = useApi()
|
||||
const stockRes = await api.post(
|
||||
`/admin/stock-locations`,
|
||||
{
|
||||
name: "Fake Warehouse 1",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
location2 = stockRes.data.stock_location.id
|
||||
|
||||
await salesChannelLocationService.associateLocation(
|
||||
"test-channel",
|
||||
location2
|
||||
)
|
||||
|
||||
const inventoryItem1 = await inventoryService.createInventoryItem({
|
||||
sku: "12345",
|
||||
})
|
||||
item2 = inventoryItem1.id
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: item2,
|
||||
location_id: location2,
|
||||
stocked_quantity: 100,
|
||||
})
|
||||
|
||||
order = await simpleOrderFactory(dbConnection, {
|
||||
sales_channel: "test-channel",
|
||||
line_items: [
|
||||
{
|
||||
variant_id: variantId,
|
||||
quantity: 2,
|
||||
id: "line-item-id-2",
|
||||
},
|
||||
],
|
||||
shipping_methods: [
|
||||
{
|
||||
shipping_option: {
|
||||
region_id: regionId,
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const orderRes = await api.get(
|
||||
`/admin/orders/${order.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
reservation2 = await inventoryService.createReservationItem({
|
||||
line_item_id: "line-item-id-2",
|
||||
inventory_item_id: item2,
|
||||
location_id: location2,
|
||||
description: "test description",
|
||||
quantity: 1,
|
||||
})
|
||||
})
|
||||
|
||||
it("lists reservation items", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(reservationsRes.data.reservations.length).toBe(2)
|
||||
expect(reservationsRes.data.reservations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: reservationItem.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: reservation2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
describe("Filters reservation items", () => {
|
||||
it("filters by location", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?location_id[]=${locationId}`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].location_id).toBe(
|
||||
locationId
|
||||
)
|
||||
})
|
||||
|
||||
it("filters by itemID", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?inventory_item_id[]=${item2}`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].inventory_item_id).toBe(
|
||||
item2
|
||||
)
|
||||
})
|
||||
|
||||
it("filters by quantity", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?quantity[gt]=1`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(
|
||||
reservationItem.id
|
||||
)
|
||||
})
|
||||
|
||||
it("filters by date", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?created_at[gte]=${new Date(
|
||||
reservation2.created_at
|
||||
).toISOString()}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(reservation2.id)
|
||||
})
|
||||
|
||||
it("filters by description using equals", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api
|
||||
.get(
|
||||
`/admin/reservations?description=test%20description`,
|
||||
adminHeaders
|
||||
)
|
||||
.catch(console.log)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(reservation2.id)
|
||||
})
|
||||
|
||||
it("filters by description using equals removes results", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?description=description`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(0)
|
||||
})
|
||||
|
||||
it("filters by description using contains", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?description[contains]=descri`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(reservation2.id)
|
||||
})
|
||||
|
||||
it("filters by description using starts_with", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api
|
||||
.get(
|
||||
`/admin/reservations?description[starts_with]=test`,
|
||||
adminHeaders
|
||||
)
|
||||
.catch(console.log)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(reservation2.id)
|
||||
})
|
||||
|
||||
it("filters by description using starts_with removes results", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?description[starts_with]=description`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(0)
|
||||
})
|
||||
|
||||
it("filters by description using ends_with", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?description[ends_with]=test`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(0)
|
||||
})
|
||||
|
||||
it("filters by description using ends_with removes results", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const reservationsRes = await api.get(
|
||||
`/admin/reservations?description[ends_with]=description`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(reservationsRes.data.reservations.length).toBe(1)
|
||||
expect(reservationsRes.data.reservations[0].id).toBe(reservation2.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it("lists reservations with inventory_items and line items", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const res = await api.get(
|
||||
`/admin/reservations?expand=line_item,inventory_item`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.reservations.length).toEqual(1)
|
||||
expect(res.data.reservations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_item: expect.objectContaining({}),
|
||||
line_item: expect.objectContaining({
|
||||
order: expect.objectContaining({}),
|
||||
}),
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,815 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../environment-helpers/use-db")
|
||||
const { getContainer } = require("../../../environment-helpers/use-container")
|
||||
const { useExpressServer } = require("../../../environment-helpers/use-api")
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
describe("Inventory Module", () => {
|
||||
let shutdownServer
|
||||
let appContainer
|
||||
let dbConnection
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Inventory Module Interface", () => {
|
||||
it("createInventoryItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
expect(inventoryItem).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
hs_code: "hs_code 123",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
requires_shipping: true,
|
||||
metadata: { abc: 123 },
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("updateInventoryItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const item = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
const updatedInventoryItem = await inventoryService.updateInventoryItem(
|
||||
item.id,
|
||||
{
|
||||
origin_country: "CZ",
|
||||
mid_code: "mid code 345",
|
||||
material: "lycra and polyester",
|
||||
weight: 500,
|
||||
metadata: {
|
||||
dce: 456,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(updatedInventoryItem).toEqual(
|
||||
expect.objectContaining({
|
||||
id: item.id,
|
||||
sku: item.sku,
|
||||
origin_country: "CZ",
|
||||
hs_code: item.hs_code,
|
||||
mid_code: "mid code 345",
|
||||
material: "lycra and polyester",
|
||||
weight: 500,
|
||||
length: item.length,
|
||||
height: item.height,
|
||||
width: item.width,
|
||||
requires_shipping: true,
|
||||
metadata: { dce: 456 },
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("deleteInventoryItem and retrieveInventoryItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const item = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
await inventoryService.deleteInventoryItem(item.id)
|
||||
|
||||
const deletedItem = inventoryService.retrieveInventoryItem(item.id)
|
||||
|
||||
await expect(deletedItem).rejects.toThrow(
|
||||
`InventoryItem with id ${item.id} was not found`
|
||||
)
|
||||
})
|
||||
|
||||
it("createInventoryLevel", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
const inventoryLevel = await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 15,
|
||||
incoming_quantity: 4,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "second_location",
|
||||
stocked_quantity: 10,
|
||||
reserved_quantity: 1,
|
||||
})
|
||||
|
||||
expect(inventoryLevel).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
incoming_quantity: 4,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
metadata: null,
|
||||
reserved_quantity: 15,
|
||||
stocked_quantity: 50,
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveStockedQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
])
|
||||
).toEqual(50)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveAvailableQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
])
|
||||
).toEqual(35)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveReservedQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
])
|
||||
).toEqual(15)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveStockedQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
"second_location",
|
||||
])
|
||||
).toEqual(60)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveAvailableQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
"second_location",
|
||||
])
|
||||
).toEqual(44)
|
||||
|
||||
expect(
|
||||
await inventoryService.retrieveReservedQuantity(inventoryItem.id, [
|
||||
"location_123",
|
||||
"second_location",
|
||||
])
|
||||
).toEqual(16)
|
||||
})
|
||||
|
||||
it("updateInventoryLevel", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
const updatedLevel = await inventoryService.updateInventoryLevel(
|
||||
inventoryItem.id,
|
||||
"location_123",
|
||||
{
|
||||
stocked_quantity: 25,
|
||||
reserved_quantity: 4,
|
||||
incoming_quantity: 10,
|
||||
}
|
||||
)
|
||||
|
||||
expect(updatedLevel).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
incoming_quantity: 10,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
metadata: null,
|
||||
reserved_quantity: 4,
|
||||
stocked_quantity: 25,
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
describe("updateInventoryLevel", () => {
|
||||
it("should pass along the correct context when doing a bulk update", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
let error
|
||||
try {
|
||||
await inventoryService.updateInventoryLevels(
|
||||
[
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 25,
|
||||
reserved_quantity: 4,
|
||||
incoming_quantity: 10,
|
||||
},
|
||||
],
|
||||
{
|
||||
transactionManager: {},
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
expect(error.message).toEqual("manager.getRepository is not a function")
|
||||
})
|
||||
|
||||
it("should pass along the correct context when doing a single update", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
let error
|
||||
try {
|
||||
await inventoryService.updateInventoryLevel(
|
||||
inventoryItem.id,
|
||||
"location_123",
|
||||
{
|
||||
stocked_quantity: 25,
|
||||
reserved_quantity: 4,
|
||||
incoming_quantity: 10,
|
||||
},
|
||||
{
|
||||
transactionManager: {},
|
||||
}
|
||||
)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
expect(error.message).toEqual("manager.getRepository is not a function")
|
||||
})
|
||||
})
|
||||
|
||||
it("deleteInventoryLevel", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
origin_country: "CH",
|
||||
mid_code: "mid code",
|
||||
material: "lycra",
|
||||
weight: 100,
|
||||
length: 200,
|
||||
height: 50,
|
||||
width: 50,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
hs_code: "hs_code 123",
|
||||
requires_shipping: true,
|
||||
})
|
||||
|
||||
const inventoryLevel = await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: "location_123",
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
await inventoryService.deleteInventoryLevel(
|
||||
inventoryItem.id,
|
||||
"location_123"
|
||||
)
|
||||
|
||||
const deletedLevel = inventoryService.retrieveInventoryLevel(
|
||||
inventoryItem.id,
|
||||
"location_123"
|
||||
)
|
||||
|
||||
await expect(deletedLevel).rejects.toThrow(
|
||||
`Inventory level for item ${inventoryItem.id} and location location_123 not found`
|
||||
)
|
||||
})
|
||||
|
||||
it("createReservationItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const locationId = "location_123"
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
})
|
||||
|
||||
const tryReserve = inventoryService.createReservationItem({
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 10,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
})
|
||||
|
||||
await expect(tryReserve).rejects.toThrow(
|
||||
`Item ${inventoryItem.id} is not stocked at location ${locationId}`
|
||||
)
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
const inventoryReservation = await inventoryService.createReservationItem(
|
||||
{
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 10,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(inventoryReservation).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 10,
|
||||
metadata: { abc: 123 },
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
|
||||
const [available, reserved] = await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(inventoryItem.id, [
|
||||
locationId,
|
||||
]),
|
||||
inventoryService.retrieveReservedQuantity(inventoryItem.id, locationId),
|
||||
])
|
||||
|
||||
expect(available).toEqual(40)
|
||||
expect(reserved).toEqual(10)
|
||||
})
|
||||
|
||||
it("updateReservationItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const locationId = "location_123"
|
||||
const newLocationId = "location_new"
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 50,
|
||||
reserved_quantity: 0,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: newLocationId,
|
||||
stocked_quantity: 20,
|
||||
reserved_quantity: 5,
|
||||
incoming_quantity: 0,
|
||||
})
|
||||
|
||||
const inventoryReservation = await inventoryService.createReservationItem(
|
||||
{
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 15,
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
const [available, reserved] = await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(inventoryItem.id, locationId),
|
||||
])
|
||||
|
||||
expect(available).toEqual(35)
|
||||
expect(reserved).toEqual(15)
|
||||
|
||||
const updatedReservation = await inventoryService.updateReservationItem(
|
||||
inventoryReservation.id,
|
||||
{
|
||||
quantity: 5,
|
||||
}
|
||||
)
|
||||
|
||||
expect(updatedReservation).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 5,
|
||||
metadata: { abc: 123 },
|
||||
})
|
||||
)
|
||||
|
||||
const [newAvailable, newReserved] = await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(inventoryItem.id, locationId),
|
||||
])
|
||||
|
||||
expect(newAvailable).toEqual(45)
|
||||
expect(newReserved).toEqual(5)
|
||||
|
||||
const updatedReservationLocation =
|
||||
await inventoryService.updateReservationItem(inventoryReservation.id, {
|
||||
quantity: 12,
|
||||
location_id: newLocationId,
|
||||
})
|
||||
|
||||
expect(updatedReservationLocation).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: newLocationId,
|
||||
quantity: 12,
|
||||
metadata: { abc: 123 },
|
||||
})
|
||||
)
|
||||
|
||||
const [
|
||||
oldLocationAvailable,
|
||||
oldLocationReserved,
|
||||
newLocationAvailable,
|
||||
newLocationReserved,
|
||||
] = await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(inventoryItem.id, locationId),
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
newLocationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(
|
||||
inventoryItem.id,
|
||||
newLocationId
|
||||
),
|
||||
])
|
||||
|
||||
expect(oldLocationAvailable).toEqual(50)
|
||||
expect(oldLocationReserved).toEqual(0)
|
||||
expect(newLocationAvailable).toEqual(3)
|
||||
expect(newLocationReserved).toEqual(17)
|
||||
})
|
||||
|
||||
it("deleteReservationItem and deleteReservationItemsByLineItem", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const locationId = "location_123"
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 10,
|
||||
})
|
||||
|
||||
const inventoryReservation = await inventoryService.createReservationItem(
|
||||
{
|
||||
line_item_id: "line_item_123",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 1,
|
||||
}
|
||||
)
|
||||
|
||||
for (let quant = 1; quant <= 3; quant++) {
|
||||
await inventoryService.createReservationItem({
|
||||
line_item_id: "line_item_444",
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
quantity: 1,
|
||||
})
|
||||
}
|
||||
|
||||
const [available, reserved] = await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(inventoryItem.id, locationId),
|
||||
])
|
||||
|
||||
expect(available).toEqual(6)
|
||||
expect(reserved).toEqual(4)
|
||||
|
||||
await inventoryService.deleteReservationItemsByLineItem("line_item_444")
|
||||
const [afterDeleteLineitemAvailable, afterDeleteLineitemReserved] =
|
||||
await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
])
|
||||
|
||||
expect(afterDeleteLineitemAvailable).toEqual(9)
|
||||
expect(afterDeleteLineitemReserved).toEqual(1)
|
||||
|
||||
await inventoryService.deleteReservationItem(inventoryReservation.id)
|
||||
const [afterDeleteReservationAvailable, afterDeleteReservationReserved] =
|
||||
await Promise.all([
|
||||
inventoryService.retrieveAvailableQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
inventoryService.retrieveReservedQuantity(
|
||||
inventoryItem.id,
|
||||
locationId
|
||||
),
|
||||
])
|
||||
|
||||
expect(afterDeleteReservationAvailable).toEqual(10)
|
||||
expect(afterDeleteReservationReserved).toEqual(0)
|
||||
})
|
||||
|
||||
it("confirmInventory", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const locationId = "location_123"
|
||||
const secondLocationId = "location_551"
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 10,
|
||||
reserved_quantity: 5,
|
||||
})
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: secondLocationId,
|
||||
stocked_quantity: 6,
|
||||
reserved_quantity: 1,
|
||||
})
|
||||
|
||||
expect(
|
||||
await inventoryService.confirmInventory(inventoryItem.id, locationId, 5)
|
||||
).toBeTruthy()
|
||||
|
||||
expect(
|
||||
await inventoryService.confirmInventory(
|
||||
inventoryItem.id,
|
||||
[locationId, secondLocationId],
|
||||
10
|
||||
)
|
||||
).toBeTruthy()
|
||||
|
||||
expect(
|
||||
await inventoryService.confirmInventory(inventoryItem.id, locationId, 6)
|
||||
).toBeFalsy()
|
||||
|
||||
expect(
|
||||
await inventoryService.confirmInventory(
|
||||
inventoryItem.id,
|
||||
[locationId, secondLocationId],
|
||||
11
|
||||
)
|
||||
).toBeFalsy()
|
||||
})
|
||||
|
||||
it("adjustInventory", async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const locationId = "location_123"
|
||||
const secondLocationId = "location_551"
|
||||
|
||||
const inventoryItem = await inventoryService.createInventoryItem({
|
||||
sku: "sku_1",
|
||||
})
|
||||
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 10,
|
||||
reserved_quantity: 5,
|
||||
})
|
||||
await inventoryService.createInventoryLevel({
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: secondLocationId,
|
||||
stocked_quantity: 6,
|
||||
reserved_quantity: 1,
|
||||
})
|
||||
|
||||
expect(
|
||||
await inventoryService.adjustInventory(inventoryItem.id, locationId, -5)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: locationId,
|
||||
stocked_quantity: 5,
|
||||
reserved_quantity: 5,
|
||||
incoming_quantity: 0,
|
||||
metadata: null,
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await inventoryService.adjustInventory(
|
||||
inventoryItem.id,
|
||||
secondLocationId,
|
||||
-10
|
||||
)
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
inventory_item_id: inventoryItem.id,
|
||||
location_id: secondLocationId,
|
||||
stocked_quantity: -4,
|
||||
reserved_quantity: 1,
|
||||
incoming_quantity: 0,
|
||||
metadata: null,
|
||||
deleted_at: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,286 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../environment-helpers/use-db")
|
||||
const { simpleProductFactory } = require("../../../factories")
|
||||
const { getContainer } = require("../../../environment-helpers/use-container")
|
||||
const { useExpressServer } = require("../../../environment-helpers/use-api")
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
describe("Inventory Module", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
let invItem1
|
||||
let invItem2
|
||||
let variant1
|
||||
let variant2
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
describe("ProductVariantInventoryService", () => {
|
||||
describe("attachInventoryItem", () => {
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
const inventoryService = appContainer.resolve("inventoryService")
|
||||
|
||||
const { variants } = await simpleProductFactory(dbConnection, {
|
||||
variants: [{}, {}],
|
||||
})
|
||||
|
||||
variant1 = variants[0]
|
||||
variant2 = variants[1]
|
||||
|
||||
invItem1 = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku-1",
|
||||
})
|
||||
|
||||
invItem2 = await inventoryService.createInventoryItem({
|
||||
sku: "test-sku-2",
|
||||
})
|
||||
})
|
||||
|
||||
it("should attach the single item with spread params", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
await pviService.attachInventoryItem(variant1.id, invItem1.id)
|
||||
|
||||
const variantItems = await pviService.listByVariant(variant1.id)
|
||||
expect(variantItems.length).toEqual(1)
|
||||
expect(variantItems[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem1.id,
|
||||
variant_id: variant1.id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should attach multiple inventory items and variants at once", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
await pviService.attachInventoryItem([
|
||||
{
|
||||
variantId: variant1.id,
|
||||
inventoryItemId: invItem1.id,
|
||||
},
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: invItem2.id,
|
||||
},
|
||||
])
|
||||
|
||||
const variantItems = await pviService.listByVariant([
|
||||
variant1.id,
|
||||
variant2.id,
|
||||
])
|
||||
expect(variantItems.length).toEqual(2)
|
||||
expect(variantItems).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem1.id,
|
||||
variant_id: variant1.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
variant_id: variant2.id,
|
||||
inventory_item_id: invItem2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should skip existing attachments when attaching a singular inventory item", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
await pviService.attachInventoryItem(variant1.id, invItem1.id)
|
||||
await pviService.attachInventoryItem(variant1.id, invItem1.id)
|
||||
|
||||
const variantItems = await pviService.listByVariant(variant1.id)
|
||||
expect(variantItems.length).toEqual(1)
|
||||
expect(variantItems[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem1.id,
|
||||
variant_id: variant1.id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should skip existing attachments when attaching multiple inventory items in bulk", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
await pviService.attachInventoryItem(variant1.id, invItem1.id)
|
||||
|
||||
await pviService.attachInventoryItem([
|
||||
{
|
||||
variantId: variant1.id,
|
||||
inventoryItemId: invItem1.id,
|
||||
},
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: invItem2.id,
|
||||
},
|
||||
])
|
||||
|
||||
const variantItems = await pviService.listByVariant([
|
||||
variant1.id,
|
||||
variant2.id,
|
||||
])
|
||||
expect(variantItems.length).toEqual(2)
|
||||
expect(variantItems).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
inventory_item_id: invItem1.id,
|
||||
variant_id: variant1.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
variant_id: variant2.id,
|
||||
inventory_item_id: invItem2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to attach items when a single item has a required_quantity below 1", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
let e
|
||||
try {
|
||||
await pviService.attachInventoryItem(variant1.id, invItem1.id, 0)
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`"requiredQuantity" must be greater than 0, the following entries are invalid: ${JSON.stringify(
|
||||
{
|
||||
variantId: variant1.id,
|
||||
inventoryItemId: invItem1.id,
|
||||
requiredQuantity: 0,
|
||||
}
|
||||
)}`
|
||||
)
|
||||
|
||||
try {
|
||||
await pviService.attachInventoryItem([
|
||||
{
|
||||
variantId: variant1.id,
|
||||
inventoryItemId: invItem1.id,
|
||||
},
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: invItem2.id,
|
||||
requiredQuantity: 0,
|
||||
},
|
||||
])
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`"requiredQuantity" must be greater than 0, the following entries are invalid: ${JSON.stringify(
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: invItem2.id,
|
||||
requiredQuantity: 0,
|
||||
}
|
||||
)}`
|
||||
)
|
||||
})
|
||||
|
||||
it("should fail to attach items when attaching to a non-existing variant", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
let e
|
||||
try {
|
||||
await pviService.attachInventoryItem("variant1.id", invItem1.id)
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`Variants not found for the following ids: variant1.id`
|
||||
)
|
||||
|
||||
try {
|
||||
await pviService.attachInventoryItem([
|
||||
{
|
||||
variantId: "variant1.id",
|
||||
inventoryItemId: invItem1.id,
|
||||
},
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: invItem2.id,
|
||||
},
|
||||
])
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`Variants not found for the following ids: variant1.id`
|
||||
)
|
||||
})
|
||||
it("should fail to attach items when attaching to a non-existing inventory item", async () => {
|
||||
const pviService = appContainer.resolve(
|
||||
"productVariantInventoryService"
|
||||
)
|
||||
|
||||
let e
|
||||
try {
|
||||
await pviService.attachInventoryItem(variant1.id, "invItem1.id")
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`Inventory items not found for the following ids: invItem1.id`
|
||||
)
|
||||
|
||||
try {
|
||||
await pviService.attachInventoryItem([
|
||||
{
|
||||
variantId: variant1.id,
|
||||
inventoryItemId: invItem1.id,
|
||||
},
|
||||
{
|
||||
variantId: variant2.id,
|
||||
inventoryItemId: "invItem2.id",
|
||||
},
|
||||
])
|
||||
} catch (err) {
|
||||
e = err
|
||||
}
|
||||
|
||||
expect(e.message).toEqual(
|
||||
`Inventory items not found for the following ids: invItem2.id`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,181 +0,0 @@
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { useApi } from "../../../environment-helpers/use-api"
|
||||
import { AxiosInstance } from "axios"
|
||||
import adminSeeder from "../../../helpers/admin-seeder"
|
||||
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../environment-helpers/use-db")
|
||||
const { getContainer } = require("../../../environment-helpers/use-container")
|
||||
const { useExpressServer } = require("../../../environment-helpers/use-api")
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
describe("List stock locations", () => {
|
||||
let shutdownServer
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let addressId
|
||||
let scId
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const stockLocationModule = appContainer.resolve(
|
||||
ModuleRegistrationName.STOCK_LOCATION
|
||||
)
|
||||
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
const { id: sc_id } = await salesChannelService.create({ name: "test" })
|
||||
scId = sc_id
|
||||
|
||||
const [{ address_id }, { id }] = await Promise.all([
|
||||
stockLocationModule.create({
|
||||
name: "Copenhagen",
|
||||
address: {
|
||||
address_1: "test street 1",
|
||||
country_code: "DK",
|
||||
},
|
||||
}),
|
||||
stockLocationModule.create({
|
||||
name: "Berlin",
|
||||
}),
|
||||
stockLocationModule.create({
|
||||
name: "Paris",
|
||||
}),
|
||||
])
|
||||
|
||||
await salesChannelLocationService.associateLocation(sc_id, id)
|
||||
|
||||
addressId = address_id
|
||||
})
|
||||
|
||||
describe("GET /stock-location", () => {
|
||||
it("should list all stock locations", async () => {
|
||||
const api = useApi()! as AxiosInstance
|
||||
|
||||
const result = await api.get("/admin/stock-locations", adminHeaders)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.stock_locations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "Copenhagen",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Berlin",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Paris",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should list stock locations filtered by q parameter", async () => {
|
||||
const api = useApi()! as AxiosInstance
|
||||
|
||||
const result = await api.get(
|
||||
"/admin/stock-locations?q=open",
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.count).toEqual(1)
|
||||
expect(result.data.stock_locations).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "Copenhagen",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should list stock locations filtered by addresses", async () => {
|
||||
const api = useApi()! as AxiosInstance
|
||||
|
||||
const result = await api.get(
|
||||
`/admin/stock-locations?address_id=${addressId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.count).toEqual(1)
|
||||
expect(result.data.stock_locations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
name: "Copenhagen",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should list stock locations filtered by sales channel Id", async () => {
|
||||
const api = useApi()! as AxiosInstance
|
||||
|
||||
const result = await api.get(
|
||||
`/admin/stock-locations?sales_channel_id=${scId}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.count).toEqual(1)
|
||||
expect(result.data.stock_locations).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "Berlin",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should list stock locations in requested order", async () => {
|
||||
const api = useApi()! as AxiosInstance
|
||||
|
||||
const nameOrderLocations = [
|
||||
expect.objectContaining({
|
||||
name: "Berlin",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Copenhagen",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "Paris",
|
||||
}),
|
||||
]
|
||||
|
||||
let result = await api.get(
|
||||
"/admin/stock-locations?order=name",
|
||||
adminHeaders
|
||||
)
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.stock_locations).toEqual(nameOrderLocations)
|
||||
|
||||
result = await api.get("/admin/stock-locations?order=-name", adminHeaders)
|
||||
expect(result.status).toEqual(200)
|
||||
expect(result.data.stock_locations).toEqual(nameOrderLocations.reverse())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,88 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Sales channels", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Stock Locations", () => {
|
||||
it("When deleting a sales channel, removes all associated locations with it", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
const api = useApi()
|
||||
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "warehouse",
|
||||
})
|
||||
const loc2 = await stockLocationService.create({
|
||||
name: "other place",
|
||||
})
|
||||
|
||||
const sc = await salesChannelService.create({ name: "channel test" })
|
||||
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc.id)
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc2.id)
|
||||
|
||||
expect(await salesChannelService.retrieve(sc.id)).toEqual(
|
||||
expect.objectContaining({
|
||||
id: sc.id,
|
||||
name: "channel test",
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(sc.id)
|
||||
).toHaveLength(2)
|
||||
|
||||
await api.delete(`/admin/sales-channels/${sc.id}`, {
|
||||
headers: { "x-medusa-access-token": "test_token" },
|
||||
})
|
||||
|
||||
await expect(salesChannelService.retrieve(sc.id)).rejects.toThrowError()
|
||||
|
||||
await expect(
|
||||
salesChannelLocationService.listLocationIds(sc.id)
|
||||
).rejects.toThrowError()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,96 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Sales channels", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Stock Locations", () => {
|
||||
it("When deleting a stock location, removes all associated sales channels with it", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
const api = useApi()
|
||||
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "warehouse",
|
||||
})
|
||||
|
||||
const saleChannel = await salesChannelService.create({
|
||||
name: "channel test",
|
||||
})
|
||||
|
||||
const otherChannel = await salesChannelService.create({
|
||||
name: "yet another channel",
|
||||
})
|
||||
|
||||
await salesChannelLocationService.associateLocation(
|
||||
saleChannel.id,
|
||||
loc.id
|
||||
)
|
||||
await salesChannelLocationService.associateLocation(
|
||||
otherChannel.id,
|
||||
loc.id
|
||||
)
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(saleChannel.id)
|
||||
).toHaveLength(1)
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(otherChannel.id)
|
||||
).toHaveLength(1)
|
||||
|
||||
await api.delete(`/admin/stock-locations/${loc.id}`, {
|
||||
headers: { "x-medusa-access-token": "test_token" },
|
||||
})
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(saleChannel.id)
|
||||
).toHaveLength(0)
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(otherChannel.id)
|
||||
).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,162 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
useApi,
|
||||
useExpressServer,
|
||||
} = require("../../../../environment-helpers/use-api")
|
||||
|
||||
const adminSeeder = require("../../../../helpers/admin-seeder")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
const adminHeaders = { headers: { "x-medusa-access-token": "test_token" } }
|
||||
|
||||
describe("Sales channels", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
jest.clearAllMocks()
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Stock Locations", () => {
|
||||
describe("CORE", () => {
|
||||
it("When listing a sales channel, it brings all associated locations with it", async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const stockLocationService = appContainer.resolve(
|
||||
"stockLocationService"
|
||||
)
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "warehouse",
|
||||
})
|
||||
const loc2 = await stockLocationService.create({
|
||||
name: "other place",
|
||||
})
|
||||
|
||||
const sc = await salesChannelService.create({ name: "channel test" })
|
||||
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc.id)
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc2.id)
|
||||
|
||||
expect(
|
||||
await salesChannelLocationService.listLocationIds(sc.id)
|
||||
).toHaveLength(2)
|
||||
|
||||
const [channels] = await salesChannelService.listAndCount(
|
||||
{},
|
||||
{
|
||||
relations: ["locations"],
|
||||
}
|
||||
)
|
||||
const createdSC = channels.find((c) => c.id === sc.id)
|
||||
|
||||
expect(channels).toHaveLength(2)
|
||||
expect(createdSC.locations).toHaveLength(2)
|
||||
expect(createdSC).toEqual(
|
||||
expect.objectContaining({
|
||||
id: sc.id,
|
||||
name: "channel test",
|
||||
locations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
sales_channel_id: sc.id,
|
||||
location_id: loc.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
sales_channel_id: sc.id,
|
||||
location_id: loc2.id,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("API", () => {
|
||||
it("Filters stock locations based on sales channel ids", async () => {
|
||||
const api = useApi()
|
||||
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
const stockLocationService = appContainer.resolve(
|
||||
"stockLocationService"
|
||||
)
|
||||
const salesChannelService = appContainer.resolve("salesChannelService")
|
||||
const salesChannelLocationService = appContainer.resolve(
|
||||
"salesChannelLocationService"
|
||||
)
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "warehouse",
|
||||
})
|
||||
const loc2 = await stockLocationService.create({
|
||||
name: "other place",
|
||||
})
|
||||
|
||||
const sc = await salesChannelService.create({ name: "Default Channel" })
|
||||
const sc2 = await salesChannelService.create({ name: "Physical store" })
|
||||
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc.id)
|
||||
await salesChannelLocationService.associateLocation(sc.id, loc2.id)
|
||||
await salesChannelLocationService.associateLocation(sc2.id, loc2.id)
|
||||
|
||||
const defaultSalesChannelFilterRes = await api.get(
|
||||
`/admin/stock-locations?sales_channel_id=${sc.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(defaultSalesChannelFilterRes.data.stock_locations).toHaveLength(
|
||||
2
|
||||
)
|
||||
expect(defaultSalesChannelFilterRes.data.stock_locations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ name: "warehouse" }),
|
||||
expect.objectContaining({ name: "other place" }),
|
||||
])
|
||||
)
|
||||
|
||||
const physicalStoreSalesChannelFilterRes = await api.get(
|
||||
`/admin/stock-locations?sales_channel_id=${sc2.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
expect(
|
||||
physicalStoreSalesChannelFilterRes.data.stock_locations
|
||||
).toHaveLength(1)
|
||||
|
||||
expect(physicalStoreSalesChannelFilterRes.data.stock_locations).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ name: "other place" }),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,273 +0,0 @@
|
||||
const path = require("path")
|
||||
|
||||
const {
|
||||
startBootstrapApp,
|
||||
} = require("../../../../environment-helpers/bootstrap-app")
|
||||
const { initDb, useDb } = require("../../../../environment-helpers/use-db")
|
||||
const {
|
||||
getContainer,
|
||||
} = require("../../../../environment-helpers/use-container")
|
||||
const { useExpressServer } = require("../../../../environment-helpers/use-api")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Stock Location Module", () => {
|
||||
let appContainer
|
||||
let dbConnection
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd })
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
describe("Stock Location Module Interface", () => {
|
||||
it("create", async () => {
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
|
||||
expect(
|
||||
await stockLocationService.create({
|
||||
name: "first location",
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "first location",
|
||||
deleted_at: null,
|
||||
address_id: null,
|
||||
metadata: null,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await stockLocationService.create({
|
||||
name: "second location",
|
||||
metadata: {
|
||||
extra: "abc",
|
||||
},
|
||||
address: {
|
||||
address_1: "addr_1",
|
||||
address_2: "line 2",
|
||||
country_code: "DK",
|
||||
city: "city",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
},
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "second location",
|
||||
metadata: {
|
||||
extra: "abc",
|
||||
},
|
||||
address_id: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("update", async () => {
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "location",
|
||||
address: {
|
||||
address_1: "addr_1",
|
||||
address_2: "line 2",
|
||||
country_code: "DK",
|
||||
city: "city",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
},
|
||||
})
|
||||
const addressId = loc.address_id
|
||||
|
||||
expect(
|
||||
await stockLocationService.retrieve(loc.id, {
|
||||
relations: ["address"],
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: loc.id,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
deleted_at: null,
|
||||
name: "location",
|
||||
address_id: addressId,
|
||||
metadata: null,
|
||||
address: expect.objectContaining({
|
||||
id: addressId,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
deleted_at: null,
|
||||
address_1: "addr_1",
|
||||
address_2: "line 2",
|
||||
company: null,
|
||||
city: "city",
|
||||
country_code: "DK",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: { abc: 123 },
|
||||
}),
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await stockLocationService.update(loc.id, {
|
||||
name: "location name",
|
||||
address_id: addressId,
|
||||
address: {
|
||||
address_1: "addr_1 updated",
|
||||
country_code: "US",
|
||||
},
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: loc.id,
|
||||
name: "location name",
|
||||
address_id: addressId,
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await stockLocationService.retrieve(loc.id, {
|
||||
relations: ["address"],
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: loc.id,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
deleted_at: null,
|
||||
name: "location name",
|
||||
address_id: addressId,
|
||||
metadata: null,
|
||||
address: expect.objectContaining({
|
||||
id: addressId,
|
||||
created_at: expect.any(Date),
|
||||
updated_at: expect.any(Date),
|
||||
deleted_at: null,
|
||||
address_1: "addr_1 updated",
|
||||
address_2: "line 2",
|
||||
company: null,
|
||||
city: "city",
|
||||
country_code: "US",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: { abc: 123 },
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("updateAddress", async () => {
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "location",
|
||||
address: {
|
||||
address_1: "addr_1",
|
||||
address_2: "line 2",
|
||||
country_code: "DK",
|
||||
city: "city",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
},
|
||||
})
|
||||
const addressId = loc.address_id
|
||||
|
||||
expect(
|
||||
await stockLocationService.updateAddress(addressId, {
|
||||
address_1: "addr_1 updated",
|
||||
country_code: "US",
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: addressId,
|
||||
address_1: "addr_1 updated",
|
||||
address_2: "line 2",
|
||||
country_code: "US",
|
||||
city: "city",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
expect(
|
||||
await stockLocationService.retrieve(loc.id, {
|
||||
relations: ["address"],
|
||||
})
|
||||
).toEqual(
|
||||
expect.objectContaining({
|
||||
id: loc.id,
|
||||
address_id: addressId,
|
||||
address: expect.objectContaining({
|
||||
id: addressId,
|
||||
address_1: "addr_1 updated",
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("delete", async () => {
|
||||
const stockLocationService = appContainer.resolve("stockLocationService")
|
||||
|
||||
const loc = await stockLocationService.create({
|
||||
name: "location",
|
||||
address: {
|
||||
address_1: "addr_1",
|
||||
address_2: "line 2",
|
||||
country_code: "DK",
|
||||
city: "city",
|
||||
phone: "111222333",
|
||||
province: "province",
|
||||
postal_code: "555-714",
|
||||
metadata: {
|
||||
abc: 123,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await stockLocationService.delete(loc.id)
|
||||
|
||||
const deletedItem = stockLocationService.retrieve(loc.id)
|
||||
|
||||
await expect(deletedItem).rejects.toThrow(
|
||||
`StockLocation with id ${loc.id} was not found`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,96 +0,0 @@
|
||||
import {
|
||||
CreateInventoryItemActions,
|
||||
createInventoryItems,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IInventoryService, WorkflowTypes } from "@medusajs/types"
|
||||
|
||||
import { pipe } from "@medusajs/workflows-sdk"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("CreateInventoryItem workflow", function () {
|
||||
let medusaContainer
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd, skipExpressListen: true })
|
||||
medusaContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
it("should compensate all the invoke if something fails", async () => {
|
||||
const workflow = createInventoryItems(medusaContainer)
|
||||
|
||||
workflow.appendAction(
|
||||
"fail_step",
|
||||
CreateInventoryItemActions.createInventoryItems,
|
||||
{
|
||||
invoke: pipe({}, async function failStep() {
|
||||
throw new Error(`Failed`)
|
||||
}),
|
||||
},
|
||||
{
|
||||
noCompensation: true,
|
||||
}
|
||||
)
|
||||
|
||||
const input: WorkflowTypes.InventoryWorkflow.CreateInventoryItemsWorkflowInputDTO =
|
||||
{
|
||||
inventoryItems: [
|
||||
{
|
||||
sku: "TABLE_LEG",
|
||||
description: "Table Leg",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const { result, errors, transaction } = await workflow.run({
|
||||
input,
|
||||
context: {},
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
action: "fail_step",
|
||||
handlerType: "invoke",
|
||||
error: expect.objectContaining({ message: `Failed` }),
|
||||
},
|
||||
])
|
||||
|
||||
expect(transaction.getState()).toEqual("reverted")
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0].inventoryItem).toEqual(
|
||||
expect.objectContaining({ id: expect.any(String) })
|
||||
)
|
||||
|
||||
const inventoryService: IInventoryService = medusaContainer.resolve(
|
||||
ModuleRegistrationName.INVENTORY
|
||||
)
|
||||
|
||||
const [inventoryItems] = await inventoryService.listInventoryItems(
|
||||
{ id: result[0].inventoryItem.id },
|
||||
{ withDeleted: true }
|
||||
)
|
||||
|
||||
expect(inventoryItems[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: result[0].inventoryItem.id,
|
||||
deleted_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,138 +0,0 @@
|
||||
import {
|
||||
CreateProductsActions,
|
||||
Handlers,
|
||||
createProducts,
|
||||
} from "@medusajs/core-flows"
|
||||
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||
import { IProductModuleService, WorkflowTypes } from "@medusajs/types"
|
||||
import { pipe } from "@medusajs/workflows-sdk"
|
||||
import path from "path"
|
||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
describe.skip("CreateProduct workflow", function () {
|
||||
let medusaContainer
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd, skipExpressListen: true })
|
||||
medusaContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
it("should compensate all the invoke if something fails", async () => {
|
||||
const workflow = createProducts(medusaContainer)
|
||||
|
||||
workflow.appendAction(
|
||||
"fail_step",
|
||||
CreateProductsActions.attachInventoryItems,
|
||||
{
|
||||
invoke: pipe({}, async function failStep() {
|
||||
throw new Error(`Failed to create products`)
|
||||
}),
|
||||
},
|
||||
{
|
||||
noCompensation: true,
|
||||
}
|
||||
)
|
||||
|
||||
const input: WorkflowTypes.ProductWorkflow.CreateProductsWorkflowInputDTO =
|
||||
{
|
||||
products: [
|
||||
{
|
||||
title: "Test product",
|
||||
type: { value: "physical" },
|
||||
tags: [{ value: "test" }],
|
||||
subtitle: "Test subtitle",
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
prices: [
|
||||
{
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
options: [
|
||||
{
|
||||
title: "Test option",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const manager = medusaContainer.resolve("manager")
|
||||
const context = {
|
||||
manager,
|
||||
}
|
||||
|
||||
const { result, errors, transaction } = await workflow.run({
|
||||
input,
|
||||
context,
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
action: "fail_step",
|
||||
handlerType: "invoke",
|
||||
error: expect.objectContaining({
|
||||
message: `Failed to create products`,
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
expect(transaction.getState()).toEqual("reverted")
|
||||
|
||||
expect(result).toHaveLength(1)
|
||||
expect(result[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
})
|
||||
)
|
||||
|
||||
const productId = result[0].id
|
||||
|
||||
let [product] = await Handlers.ProductHandlers.listProducts({
|
||||
container: medusaContainer,
|
||||
context,
|
||||
data: {
|
||||
ids: [productId],
|
||||
},
|
||||
} as any)
|
||||
|
||||
expect(product).toBeUndefined()
|
||||
|
||||
const productModule = medusaContainer.resolve(
|
||||
ModuleRegistrationName.PRODUCT
|
||||
) as IProductModuleService
|
||||
|
||||
;[product] = await productModule.list(
|
||||
{
|
||||
id: productId,
|
||||
},
|
||||
{
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(product).toEqual(
|
||||
expect.objectContaining({
|
||||
deleted_at: expect.any(Date),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,114 +0,0 @@
|
||||
import {
|
||||
Handlers,
|
||||
updateProducts,
|
||||
UpdateProductsActions,
|
||||
} from "@medusajs/core-flows"
|
||||
import { WorkflowTypes } from "@medusajs/types"
|
||||
import { pipe } from "@medusajs/workflows-sdk"
|
||||
import path from "path"
|
||||
|
||||
import { startBootstrapApp } from "../../../../environment-helpers/bootstrap-app"
|
||||
import { getContainer } from "../../../../environment-helpers/use-container"
|
||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||
import { simpleProductFactory } from "../../../../factories"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
describe.skip("UpdateProduct workflow", function () {
|
||||
let dbConnection
|
||||
let medusaContainer
|
||||
let shutdownServer
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
shutdownServer = await startBootstrapApp({ cwd, skipExpressListen: true })
|
||||
medusaContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
await shutdownServer()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await simpleProductFactory(dbConnection, {
|
||||
title: "Original title",
|
||||
id: "to-update",
|
||||
variants: [{ id: "original-variant" }],
|
||||
})
|
||||
})
|
||||
|
||||
it("should compensate all the invoke if something fails", async () => {
|
||||
const workflow = updateProducts(medusaContainer)
|
||||
|
||||
workflow.appendAction(
|
||||
"fail_step",
|
||||
UpdateProductsActions.removeInventoryItems,
|
||||
{
|
||||
invoke: pipe({}, async function failStep() {
|
||||
throw new Error(`Failed to update products`)
|
||||
}),
|
||||
},
|
||||
{
|
||||
noCompensation: true,
|
||||
}
|
||||
)
|
||||
|
||||
const input: WorkflowTypes.ProductWorkflow.UpdateProductsWorkflowInputDTO =
|
||||
{
|
||||
products: [
|
||||
{
|
||||
id: "to-update",
|
||||
title: "Updated title",
|
||||
variants: [
|
||||
{
|
||||
title: "Should be deleted with revert variant",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const manager = medusaContainer.resolve("manager")
|
||||
const context = {
|
||||
manager,
|
||||
}
|
||||
|
||||
const { errors, transaction } = await workflow.run({
|
||||
input,
|
||||
context,
|
||||
throwOnError: false,
|
||||
})
|
||||
|
||||
expect(errors).toEqual([
|
||||
{
|
||||
action: "fail_step",
|
||||
handlerType: "invoke",
|
||||
error: expect.objectContaining({
|
||||
message: `Failed to update products`,
|
||||
}),
|
||||
},
|
||||
])
|
||||
|
||||
expect(transaction.getState()).toEqual("reverted")
|
||||
|
||||
let [product] = await Handlers.ProductHandlers.listProducts({
|
||||
container: medusaContainer,
|
||||
context,
|
||||
data: {
|
||||
ids: ["to-update"],
|
||||
config: { listConfig: { relations: ["variants"] } },
|
||||
},
|
||||
} as any)
|
||||
|
||||
expect(product).toEqual(
|
||||
expect.objectContaining({
|
||||
title: "Original title",
|
||||
id: "to-update",
|
||||
variants: [expect.objectContaining({ id: "original-variant" })],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,100 +0,0 @@
|
||||
const { useApi } = require("../../helpers/use-api")
|
||||
|
||||
const header = {
|
||||
headers: {
|
||||
"x-medusa-access-token": "test_token",
|
||||
},
|
||||
}
|
||||
|
||||
const resolveCall = async (path, payload, header) => {
|
||||
const api = useApi()
|
||||
let res
|
||||
try {
|
||||
const resp = await api.post(path, payload, header)
|
||||
res = resp.status
|
||||
} catch (expectedException) {
|
||||
try {
|
||||
res = expectedException.response.status
|
||||
} catch (_) {
|
||||
console.error(expectedException)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
const determineFail = (actual, expected, path) => {
|
||||
if (expected !== actual) {
|
||||
console.log(`failed at path : ${path}`)
|
||||
}
|
||||
expect(actual).toEqual(expected)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to wrap a Call function so that you may reuse some input values.
|
||||
* @param {Function} fun - the function to call with partial information
|
||||
* @param {Object} input - the constant input which we want to supply now
|
||||
* @returns
|
||||
*/
|
||||
module.exports.partial = function (fun, input = {}) {
|
||||
return async (remaining) => await fun({ ...remaining, ...input })
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to assert a specific code result from a POST call.
|
||||
* @param {Object} input - the information needed to make the call
|
||||
* (path & payload) and the expected code (code)
|
||||
*/
|
||||
module.exports.expectPostCallToReturn = async function (
|
||||
input = {
|
||||
code,
|
||||
path,
|
||||
payload: {},
|
||||
}
|
||||
) {
|
||||
const res = await resolveCall(input.path, input.payload, header)
|
||||
determineFail(res, input.code, input.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to assert a specific code result from multiple POST
|
||||
* calls.
|
||||
* @param {Object} input - the collection of objects to execute
|
||||
* calls from (col), a function which yields the path (pathf),
|
||||
* and another one which provides the payload (payloadf), as
|
||||
* well as the code (code) which we want to assert.
|
||||
*/
|
||||
module.exports.expectAllPostCallsToReturn = async function (
|
||||
input = {
|
||||
code,
|
||||
col,
|
||||
pathf,
|
||||
payloadf,
|
||||
}
|
||||
) {
|
||||
for (const i of input.col) {
|
||||
const res = await resolveCall(
|
||||
input.pathf(i),
|
||||
input.payloadf ? input.payloadf(i) : {},
|
||||
header
|
||||
)
|
||||
determineFail(res, input.code, input.pathf(i))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to retrieve a specific object the response
|
||||
* from get call,
|
||||
* and simultaneously assert that the call was successful.
|
||||
* @param {Object} param0 - contains the path which to
|
||||
* call (path), and the object within the response.data (get)
|
||||
* we want to retrieve.
|
||||
* @returns {Object} found within response.data corresponding
|
||||
* to the get parameter provided.
|
||||
*/
|
||||
module.exports.callGet = async function ({ path, get }) {
|
||||
const api = useApi()
|
||||
const res = await api.get(path, header)
|
||||
|
||||
determineFail(res.status, 200, path)
|
||||
return res?.data[get]
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
process.chdir(__dirname)
|
||||
|
||||
module.exports = {
|
||||
name: "Plugins",
|
||||
testEnvironment: `node`,
|
||||
rootDir: "./",
|
||||
testPathIgnorePatterns: [
|
||||
`/examples/`,
|
||||
`/www/`,
|
||||
`/dist/`,
|
||||
`/node_modules/`,
|
||||
`<rootDir>/node_modules/`,
|
||||
`__tests__/fixtures`,
|
||||
`__testfixtures__`,
|
||||
`.cache`,
|
||||
],
|
||||
transformIgnorePatterns: ["/dist", "/node_modules/"],
|
||||
transform: { "^.+\\.[jt]s$": `@swc/jest` },
|
||||
setupFiles: ["../setup-env.js"],
|
||||
setupFilesAfterEnv: ["../setup.js"],
|
||||
globalSetup: "../globalSetup.js",
|
||||
globalTeardown: "../globalTeardown.js",
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
const { Modules } = require("@medusajs/modules-sdk")
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = process.env.DB_TEMP_NAME
|
||||
const DB_URL = `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`
|
||||
|
||||
process.env.POSTGRES_URL = DB_URL
|
||||
process.env.LOG_LEVEL = "error"
|
||||
|
||||
const enableMedusaV2 = process.env.MEDUSA_FF_MEDUSA_V2 == "true"
|
||||
|
||||
module.exports = {
|
||||
plugins: [],
|
||||
projectConfig: {
|
||||
// redis_url: REDIS_URL,
|
||||
database_url: DB_URL,
|
||||
database_type: "postgres",
|
||||
jwt_secret: "test",
|
||||
cookie_secret: "test",
|
||||
database_extra: { idle_in_transaction_session_timeout: 0 },
|
||||
},
|
||||
featureFlags: {
|
||||
medusa_v2: enableMedusaV2,
|
||||
},
|
||||
modules: {
|
||||
workflows: true,
|
||||
|
||||
[Modules.AUTH]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/auth",
|
||||
options: {
|
||||
providers: [
|
||||
{
|
||||
name: "emailpass",
|
||||
scopes: {
|
||||
admin: {},
|
||||
store: {},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
[Modules.STOCK_LOCATION]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/stock-location",
|
||||
},
|
||||
[Modules.INVENTORY]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/inventory",
|
||||
},
|
||||
[Modules.PRICING]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/pricing",
|
||||
},
|
||||
[Modules.CACHE]: {
|
||||
resolve: "@medusajs/cache-inmemory",
|
||||
options: { ttl: 0 }, // Cache disabled
|
||||
},
|
||||
[Modules.PRODUCT]: {
|
||||
scope: "internal",
|
||||
resources: "shared",
|
||||
resolve: "@medusajs/product",
|
||||
},
|
||||
[Modules.WORKFLOW_ENGINE]: true,
|
||||
},
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"name": "integration-tests-plugins",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:integration": "jest --silent=false --maxWorkers=50% --bail --detectOpenHandles --clearMocks --forceExit",
|
||||
"build": "babel src -d dist --extensions \".ts,.js\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/api-key": "workspace:^",
|
||||
"@medusajs/auth": "workspace:*",
|
||||
"@medusajs/cache-inmemory": "workspace:*",
|
||||
"@medusajs/currency": "workspace:^",
|
||||
"@medusajs/customer": "workspace:^",
|
||||
"@medusajs/event-bus-local": "workspace:*",
|
||||
"@medusajs/inventory": "latest",
|
||||
"@medusajs/inventory-next": "workspace:^",
|
||||
"@medusajs/medusa": "workspace:*",
|
||||
"@medusajs/modules-sdk": "workspace:^",
|
||||
"@medusajs/pricing": "workspace:^",
|
||||
"@medusajs/product": "workspace:^",
|
||||
"@medusajs/promotion": "workspace:^",
|
||||
"@medusajs/region": "workspace:^",
|
||||
"@medusajs/stock-location": "latest",
|
||||
"@medusajs/store": "workspace:^",
|
||||
"@medusajs/tax": "workspace:^",
|
||||
"@medusajs/user": "workspace:^",
|
||||
"@medusajs/utils": "workspace:^",
|
||||
"@medusajs/workflow-engine-inmemory": "workspace:*",
|
||||
"faker": "^5.5.3",
|
||||
"medusa-interfaces": "workspace:*",
|
||||
"pg": "^8.11.0",
|
||||
"typeorm": "^0.3.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"@medusajs/types": "workspace:^",
|
||||
"@swc/core": "^1.4.8",
|
||||
"@swc/jest": "^0.2.36",
|
||||
"babel-preset-medusa-package": "*",
|
||||
"jest": "^26.6.3",
|
||||
"jest-environment-node": "26.6.2"
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
import { AbstractFileService } from "@medusajs/medusa"
|
||||
import * as fs from "fs"
|
||||
import mkdirp from "mkdirp"
|
||||
import { resolve } from "path"
|
||||
import stream from "stream"
|
||||
|
||||
export default class LocalFileService extends AbstractFileService {
|
||||
constructor({}, options) {
|
||||
super({}, options)
|
||||
this.upload_dir_ =
|
||||
process.env.UPLOAD_DIR ?? options.upload_dir ?? "uploads/images"
|
||||
|
||||
if (!fs.existsSync(this.upload_dir_)) {
|
||||
fs.mkdirSync(this.upload_dir_)
|
||||
}
|
||||
}
|
||||
|
||||
upload(file) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, file.originalname)
|
||||
|
||||
let content = ""
|
||||
if (file.filename) {
|
||||
content = fs.readFileSync(
|
||||
resolve(process.cwd(), "uploads", file.filename)
|
||||
)
|
||||
}
|
||||
|
||||
const pathSegments = path.split("/")
|
||||
pathSegments.splice(-1)
|
||||
const dirname = pathSegments.join("/")
|
||||
mkdirp.sync(dirname, { recursive: true })
|
||||
|
||||
fs.writeFile(path, content.toString(), (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise({ url: path })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
delete({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
fs.unlink(path, (err) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
|
||||
resolvePromise("file unlinked")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async getUploadStreamDescriptor({ name, ext }) {
|
||||
const fileKey = `${name}.${ext}`
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
|
||||
const isFileExists = fs.existsSync(path)
|
||||
if (!isFileExists) {
|
||||
await this.upload({ originalname: fileKey })
|
||||
}
|
||||
|
||||
const pass = new stream.PassThrough()
|
||||
pass.pipe(fs.createWriteStream(path))
|
||||
|
||||
return {
|
||||
writeStream: pass,
|
||||
promise: Promise.resolve(),
|
||||
url: `${this.upload_dir_}/${fileKey}`,
|
||||
fileKey,
|
||||
}
|
||||
}
|
||||
|
||||
async getDownloadStream({ fileKey }) {
|
||||
return new Promise((resolvePromise, reject) => {
|
||||
try {
|
||||
const path = resolve(this.upload_dir_, fileKey)
|
||||
const data = fs.readFileSync(path)
|
||||
const readable = stream.Readable()
|
||||
readable._read = function () {}
|
||||
readable.push(data.toString())
|
||||
readable.push(null)
|
||||
resolvePromise(readable)
|
||||
} catch (e) {
|
||||
reject(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async getPresignedDownloadUrl({ fileKey }) {
|
||||
return `${this.upload_dir_}/${fileKey}`
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { FulfillmentService } from "medusa-interfaces"
|
||||
|
||||
class TestFulService extends FulfillmentService {
|
||||
static identifier = "test-ful"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
getFulfillmentOptions() {
|
||||
return [
|
||||
{
|
||||
id: "manual-fulfillment",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
validateFulfillmentData(data, cart) {
|
||||
return data
|
||||
}
|
||||
|
||||
validateOption(data) {
|
||||
return true
|
||||
}
|
||||
|
||||
canCalculate() {
|
||||
return false
|
||||
}
|
||||
|
||||
calculatePrice() {
|
||||
throw Error("Manual Fulfillment service cannot calculatePrice")
|
||||
}
|
||||
|
||||
createOrder() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createReturn() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
createFulfillment() {
|
||||
// No data is being sent anywhere
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
cancelFulfillment() {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
}
|
||||
|
||||
export default TestFulService
|
||||
@@ -1,19 +0,0 @@
|
||||
import { NotificationService } from "medusa-interfaces"
|
||||
|
||||
class TestNotiService extends NotificationService {
|
||||
static identifier = "test-not"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
async sendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
async resendNotification() {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
export default TestNotiService
|
||||
@@ -1,73 +0,0 @@
|
||||
import { AbstractPaymentService } from "@medusajs/medusa"
|
||||
|
||||
class TestPayService extends AbstractPaymentService {
|
||||
static identifier = "test-pay"
|
||||
|
||||
constructor(_) {
|
||||
super(_)
|
||||
}
|
||||
|
||||
async getStatus(paymentData) {
|
||||
return "authorized"
|
||||
}
|
||||
|
||||
async retrieveSavedMethods(customer) {
|
||||
return []
|
||||
}
|
||||
|
||||
async createPayment(cart) {
|
||||
const fields = [
|
||||
"total",
|
||||
"subtotal",
|
||||
"tax_total",
|
||||
"discount_total",
|
||||
"shipping_total",
|
||||
"gift_card_total",
|
||||
]
|
||||
|
||||
const data = {}
|
||||
for (const k of fields) {
|
||||
data[k] = cart[k]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
async retrievePayment(data) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPaymentData(sessionData) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async authorizePayment(sessionData, context = {}) {
|
||||
return { data: {}, status: "authorized" }
|
||||
}
|
||||
|
||||
async updatePaymentData(sessionData, update) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async updatePayment(sessionData, cart) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async deletePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async capturePayment(payment) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async refundPayment(payment, amountToRefund) {
|
||||
return {}
|
||||
}
|
||||
|
||||
async cancelPayment(payment) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default TestPayService
|
||||
@@ -1,231 +0,0 @@
|
||||
import { ProductCategoryRepository } from "@medusajs/medusa/dist/repositories/product-category"
|
||||
import path from "path"
|
||||
import { initDb, useDb } from "../../../environment-helpers/use-db"
|
||||
import { simpleProductCategoryFactory } from "../../../factories"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Categories", () => {
|
||||
let dbConnection
|
||||
|
||||
beforeAll(async () => {
|
||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
||||
dbConnection = await initDb({ cwd })
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
const db = useDb()
|
||||
await db.shutdown()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
describe("Tree Queries (Materialized Paths)", () => {
|
||||
let a1, a11, a111, a12
|
||||
let productCategoryRepository
|
||||
|
||||
beforeEach(async () => {
|
||||
a1 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "a1",
|
||||
is_active: true,
|
||||
rank: 0,
|
||||
})
|
||||
a11 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "a11",
|
||||
parent_category: a1,
|
||||
is_active: true,
|
||||
rank: 0,
|
||||
})
|
||||
a111 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "a111",
|
||||
parent_category: a11,
|
||||
is_active: true,
|
||||
is_internal: true,
|
||||
rank: 0,
|
||||
})
|
||||
a12 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "a12",
|
||||
parent_category: a1,
|
||||
is_active: false,
|
||||
rank: 1,
|
||||
})
|
||||
|
||||
productCategoryRepository = dbConnection.manager.withRepository(
|
||||
ProductCategoryRepository
|
||||
)
|
||||
})
|
||||
|
||||
it("can fetch all root categories", async () => {
|
||||
const rootCategories = await productCategoryRepository.findRoots()
|
||||
|
||||
expect(rootCategories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "a1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("can fetch all ancestors of a category", async () => {
|
||||
const a11Parent = await productCategoryRepository.findAncestors(a11)
|
||||
|
||||
expect(a11Parent).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "a1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "a11",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("can fetch all root descendants of a category", async () => {
|
||||
const a1Children = await productCategoryRepository.findDescendants(a1)
|
||||
|
||||
expect(a1Children).toEqual([
|
||||
expect.objectContaining({
|
||||
name: "a1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "a11",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "a111",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: "a12",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("getFreeTextSearchResultsAndCount", () => {
|
||||
let a1, a11, a111, a12
|
||||
let productCategoryRepository
|
||||
|
||||
beforeEach(async () => {
|
||||
a1 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "skinny jeans",
|
||||
handle: "skinny-jeans",
|
||||
is_active: true,
|
||||
rank: 0,
|
||||
})
|
||||
|
||||
a11 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "winter shirts",
|
||||
handle: "winter-shirts",
|
||||
parent_category: a1,
|
||||
is_active: true,
|
||||
rank: 0,
|
||||
})
|
||||
|
||||
a111 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "running shoes",
|
||||
handle: "running-shoes",
|
||||
parent_category: a11,
|
||||
rank: 0,
|
||||
})
|
||||
|
||||
a12 = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "casual shoes",
|
||||
handle: "casual-shoes",
|
||||
parent_category: a1,
|
||||
is_internal: true,
|
||||
rank: 1,
|
||||
})
|
||||
|
||||
productCategoryRepository = dbConnection.manager.withRepository(
|
||||
ProductCategoryRepository
|
||||
)
|
||||
})
|
||||
|
||||
it("fetches all active categories", async () => {
|
||||
const [categories, count] =
|
||||
await productCategoryRepository.getFreeTextSearchResultsAndCount({
|
||||
where: { is_active: true },
|
||||
})
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a1.name,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: a11.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all internal categories", async () => {
|
||||
const [categories, count] =
|
||||
await productCategoryRepository.getFreeTextSearchResultsAndCount({
|
||||
where: { is_internal: true },
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all categories with query shoes", async () => {
|
||||
const [categories, count] =
|
||||
await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: {} },
|
||||
"shoes"
|
||||
)
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a111.name,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all categories with query casual-", async () => {
|
||||
const [categories, count] =
|
||||
await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: {} },
|
||||
"casual-"
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("builds relations for categories", async () => {
|
||||
const [categories, count] =
|
||||
await productCategoryRepository.getFreeTextSearchResultsAndCount({
|
||||
where: { id: a11.id },
|
||||
relations: { parent_category: true, category_children: true },
|
||||
})
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: a11.id,
|
||||
parent_category: expect.objectContaining({
|
||||
id: a1.id,
|
||||
}),
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: a111.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
name: "repositories",
|
||||
testEnvironment: `node`,
|
||||
rootDir: "./",
|
||||
testTimeout: 10000,
|
||||
testPathIgnorePatterns: [
|
||||
`/examples/`,
|
||||
`/www/`,
|
||||
`/dist/`,
|
||||
`/node_modules/`,
|
||||
`__tests__/fixtures`,
|
||||
`__testfixtures__`,
|
||||
`.cache`,
|
||||
],
|
||||
transformIgnorePatterns: ["/dist", "/node_modules/"],
|
||||
transform: { "^.+\\.[jt]s$": `../../jest-transformer.js` },
|
||||
setupFiles: ["../setup-env.js"],
|
||||
setupFilesAfterEnv: ["../setup.js"],
|
||||
globalSetup: "../globalSetup.js",
|
||||
globalTeardown: "../globalTeardown.js",
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = process.env.DB_TEMP_NAME
|
||||
|
||||
module.exports = {
|
||||
plugins: [],
|
||||
projectConfig: {
|
||||
redis_url: process.env.REDIS_URL,
|
||||
database_url: `postgres://${DB_USERNAME}:${DB_PASSWORD}@${DB_HOST}/${DB_NAME}`,
|
||||
database_type: "postgres",
|
||||
jwt_secret: "test",
|
||||
cookie_secret: "test",
|
||||
},
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
{
|
||||
"name": "integration-tests-repositories",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test:integration": "jest --silent=false --runInBand --bail --detectOpenHandles --forceExit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@medusajs/cache-inmemory": "workspace:*",
|
||||
"@medusajs/event-bus-local": "workspace:*",
|
||||
"@medusajs/medusa": "workspace:*",
|
||||
"medusa-interfaces": "workspace:*",
|
||||
"pg": "^8.11.0",
|
||||
"typeorm": "^0.3.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.12.10",
|
||||
"@babel/core": "^7.12.10",
|
||||
"@babel/node": "^7.12.10",
|
||||
"babel-preset-medusa-package": "*",
|
||||
"jest": "^26.6.3",
|
||||
"jest-environment-node": "26.6.2"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,15 @@ const path = require(`path`)
|
||||
const glob = require(`glob`)
|
||||
const fs = require(`fs`)
|
||||
|
||||
const pkgs = glob.sync(`./packages/*`).map((p) => p.replace(/^\./, `<rootDir>`))
|
||||
const pkgs = [
|
||||
glob.sync(`./packages/*`).map((p) => p.replace(/^\./, `<rootDir>`)),
|
||||
glob.sync(`./packages/cli/*`).map((p) => p.replace(/^\./, `<rootDir>`)),
|
||||
glob.sync(`./packages/core/*`).map((p) => p.replace(/^\./, `<rootDir>`)),
|
||||
glob.sync(`./packages/modules/*`).map((p) => p.replace(/^\./, `<rootDir>`)),
|
||||
glob
|
||||
.sync(`./packages/modules/providers/*`)
|
||||
.map((p) => p.replace(/^\./, `<rootDir>`)),
|
||||
].flat(Infinity)
|
||||
|
||||
const reMedusa = /medusa$/
|
||||
const medusaDir = pkgs.find((p) => reMedusa.exec(p))
|
||||
@@ -26,7 +34,13 @@ module.exports = {
|
||||
notify: true,
|
||||
verbose: true,
|
||||
roots: ["<rootDir>"],
|
||||
projects: ["<rootDir>/packages/*/jest.config.js"],
|
||||
projects: [
|
||||
"<rootDir>/packages/*/jest.config.js",
|
||||
"<rootDir>/packages/cli/*/jest.config.js",
|
||||
"<rootDir>/packages/core/*/jest.config.js",
|
||||
"<rootDir>/packages/modules/*/jest.config.js",
|
||||
"<rootDir>/packages/modules/providers/*/jest.config.js",
|
||||
],
|
||||
modulePathIgnorePatterns: ignoreDirs,
|
||||
coveragePathIgnorePatterns: ignoreDirs,
|
||||
testPathIgnorePatterns: [
|
||||
|
||||
12
package.json
12
package.json
@@ -9,8 +9,6 @@
|
||||
"packages/core/*",
|
||||
"packages/cli/*",
|
||||
"packages/cli/oas/*",
|
||||
"packages/medusa-js",
|
||||
"packages/medusa-react",
|
||||
"packages/*",
|
||||
"packages/admin-next/*",
|
||||
"packages/design-system/*",
|
||||
@@ -78,15 +76,11 @@
|
||||
"test:chunk": "./scripts/run-workspace-unit-tests-in-chunks.sh",
|
||||
"test:integration:packages": "turbo run test:integration --concurrency=50% --no-daemon --no-cache --force --filter='./packages/*' --filter='./packages/core/*' --filter='./packages/cli/*' --filter='./packages/modules/*' --filter='./packages/modules/providers/*'",
|
||||
"test:integration:api": "turbo run test:integration:chunk --concurrency=50% --no-daemon --no-cache --force --filter=integration-tests-api",
|
||||
"test:integration:plugins": "turbo run test:integration --concurrency=50% --no-daemon --no-cache --filter=integration-tests-plugins",
|
||||
"test:integration:modules": "turbo run test:integration:chunk --concurrency=50% --no-daemon --no-cache --force --filter=integration-tests-modules",
|
||||
"test:integration:repositories": "turbo run test:integration --concurrency=50% --no-daemon --no-cache --filter=integration-tests-repositories",
|
||||
"openapi:generate": "yarn ./packages/cli/oas/oas-github-ci run ci --with-full-file",
|
||||
"medusa-oas": "yarn ./packages/cli/oas/medusa-oas-cli run medusa-oas",
|
||||
"openapi:generate": "yarn ./packages/oas/oas-github-ci run ci --with-full-file --v2",
|
||||
"medusa-oas": "yarn ./packages/oas/medusa-oas-cli run medusa-oas --v2",
|
||||
"release:snapshot": "changeset publish --no-git-tags --snapshot --tag snapshot",
|
||||
"develop": "ts-node --transpile-only ./integration-tests/development/server.js",
|
||||
"develop:create:db": "ts-node --transpile-only ./integration-tests/development/create-database.js",
|
||||
"release:next": "changeset publish --no-git-tags --snapshot --tag next",
|
||||
"release:next": "chgstangeset publish --no-git-tags --snapshot --tag next",
|
||||
"version:next": "changeset version --snapshot next",
|
||||
"release": "changeset publish",
|
||||
"version": "changeset version && yarn install --no-immutable"
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
"i18next-browser-languagedetector": "7.2.0",
|
||||
"i18next-http-backend": "2.4.2",
|
||||
"match-sorter": "^6.3.4",
|
||||
"medusa-react": "workspace:^",
|
||||
"medusa-react": "latest",
|
||||
"qs": "^6.12.0",
|
||||
"react": "^18.2.0",
|
||||
"react-country-flag": "^3.1.0",
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
"noEmit": true,
|
||||
"noUnusedLocals": true,
|
||||
"esModuleInterop": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"@babel/types": ["../../../node_modules/@babel/types"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export const commandOptions: Option[] = [
|
||||
),
|
||||
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
||||
new Option(
|
||||
"--v2",
|
||||
"--v2",
|
||||
"Generate OAS files for V2 endpoints. This loads OAS from www/utils/generated/oas-output/operations directory"
|
||||
),
|
||||
new Option(
|
||||
@@ -151,7 +151,7 @@ async function getOASFromCodebase(
|
||||
): Promise<OpenAPIObject> {
|
||||
/**
|
||||
* OAS output directory
|
||||
*
|
||||
*
|
||||
* @privateRemark
|
||||
* This should be the only directory OAS is loaded from for Medusa V2.
|
||||
* For now, we only use it if the --v2 flag it passed to the CLI tool.
|
||||
@@ -165,7 +165,7 @@ async function getOASFromCodebase(
|
||||
path.resolve(oasOutputPath, "schemas"),
|
||||
// We currently load error schemas from here. If we change
|
||||
// that in the future, we should change the path.
|
||||
path.resolve(medusaPackagePath, "dist", "api/middlewares"),
|
||||
path.resolve(medusaPackagePath, "dist", "utils/middlewares"),
|
||||
] : [
|
||||
path.resolve(medusaTypesPath, "dist"),
|
||||
path.resolve(medusaUtilsPath, "dist"),
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { getDatabaseURL } from "./database"
|
||||
import { initDb } from "./medusa-test-runner-utils/use-db"
|
||||
import { startBootstrapApp } from "./medusa-test-runner-utils/bootstrap-app"
|
||||
import { createDatabase, dropDatabase } from "pg-god"
|
||||
import {ContainerLike, MedusaContainer} from "@medusajs/types"
|
||||
import { ContainerLike, MedusaContainer } from "@medusajs/types"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
import { createDatabase, dropDatabase } from "pg-god"
|
||||
import { getDatabaseURL } from "./database"
|
||||
import { startBootstrapApp } from "./medusa-test-runner-utils/bootstrap-app"
|
||||
import { initDb } from "./medusa-test-runner-utils/use-db"
|
||||
|
||||
const axios = require("axios").default
|
||||
|
||||
@@ -114,12 +114,16 @@ export function medusaIntegrationTestRunner({
|
||||
) => {
|
||||
const config = originalConfigLoader(rootDirectory)
|
||||
config.projectConfig.database_url = dbConfig.clientUrl
|
||||
config.projectConfig.database_driver_options = dbConfig.clientUrl.includes("localhost") ? {} : {
|
||||
connection: {
|
||||
ssl: { rejectUnauthorized: false },
|
||||
},
|
||||
idle_in_transaction_session_timeout: 20000,
|
||||
}
|
||||
config.projectConfig.database_driver_options = dbConfig.clientUrl.includes(
|
||||
"localhost"
|
||||
)
|
||||
? {}
|
||||
: {
|
||||
connection: {
|
||||
ssl: { rejectUnauthorized: false },
|
||||
},
|
||||
idle_in_transaction_session_timeout: 20000,
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
@@ -224,19 +228,6 @@ export function medusaIntegrationTestRunner({
|
||||
const container = options.getContainer()
|
||||
const copiedContainer = createMedusaContainer({}, container)
|
||||
|
||||
if (process.env.MEDUSA_FF_MEDUSA_V2 != "true") {
|
||||
try {
|
||||
const defaultLoader =
|
||||
require("@medusajs/medusa/dist/loaders/defaults").default
|
||||
await defaultLoader({
|
||||
container: copiedContainer,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error runner medusa loaders", error?.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const medusaAppLoaderRunner =
|
||||
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
|
||||
|
||||
@@ -82,10 +82,10 @@
|
||||
"dependencies": {
|
||||
"@medusajs/icons": "^1.2.1",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.4",
|
||||
"@radix-ui/react-alert-dialog": "1.0.4",
|
||||
"@radix-ui/react-avatar": "^1.0.3",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
import type { CampaignBudget } from "./CampaignBudget"
|
||||
|
||||
/**
|
||||
* The promotion's campaign.
|
||||
*/
|
||||
export interface AdminPostCampaignsReq {
|
||||
/**
|
||||
* The campaign's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The campaign's campaign identifier.
|
||||
*/
|
||||
campaign_identifier?: string
|
||||
/**
|
||||
* The campaign's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The campaign's currency.
|
||||
*/
|
||||
currency?: string
|
||||
budget?: CampaignBudget
|
||||
/**
|
||||
* The campaign's starts at.
|
||||
*/
|
||||
starts_at?: string
|
||||
/**
|
||||
* The campaign's ends at.
|
||||
*/
|
||||
ends_at?: string
|
||||
/**
|
||||
* The campaign's promotions.
|
||||
*/
|
||||
promotions?: Array<{
|
||||
/**
|
||||
* The promotion's ID.
|
||||
*/
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The promotion's application method.
|
||||
*/
|
||||
export interface ApplicationMethod {}
|
||||
@@ -0,0 +1,69 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The promotion's application method.
|
||||
*/
|
||||
export interface ApplicationMethodsMethodPostReq {
|
||||
/**
|
||||
* The application method's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The application method's value.
|
||||
*/
|
||||
value?: string
|
||||
/**
|
||||
* The application method's max quantity.
|
||||
*/
|
||||
max_quantity?: number
|
||||
type?: "fixed" | "percentage"
|
||||
target_type?: "order" | "shipping_methods" | "items"
|
||||
allocation?: "each" | "across"
|
||||
/**
|
||||
* The application method's target rules.
|
||||
*/
|
||||
target_rules?: Array<{
|
||||
operator: "gte" | "lte" | "gt" | "lt" | "eq" | "ne" | "in"
|
||||
/**
|
||||
* The target rule's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The target rule's attribute.
|
||||
*/
|
||||
attribute: string
|
||||
/**
|
||||
* The target rule's values.
|
||||
*/
|
||||
values: Array<string>
|
||||
}>
|
||||
/**
|
||||
* The application method's buy rules.
|
||||
*/
|
||||
buy_rules?: Array<{
|
||||
operator: "gte" | "lte" | "gt" | "lt" | "eq" | "ne" | "in"
|
||||
/**
|
||||
* The buy rule's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The buy rule's attribute.
|
||||
*/
|
||||
attribute: string
|
||||
/**
|
||||
* The buy rule's values.
|
||||
*/
|
||||
values: Array<string>
|
||||
}>
|
||||
/**
|
||||
* The application method's apply to quantity.
|
||||
*/
|
||||
apply_to_quantity?: number
|
||||
/**
|
||||
* The application method's buy rules min quantity.
|
||||
*/
|
||||
buy_rules_min_quantity?: number
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The promotion's campaign.
|
||||
*/
|
||||
export interface Campaign {}
|
||||
@@ -0,0 +1,15 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The campaign's budget.
|
||||
*/
|
||||
export interface CampaignBudget {
|
||||
type?: "spend" | "usage"
|
||||
/**
|
||||
* The budget's limit.
|
||||
*/
|
||||
limit?: number
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
import type { ApplicationMethod } from "./ApplicationMethod"
|
||||
import type { Campaign } from "./Campaign"
|
||||
|
||||
/**
|
||||
* The promotion's application method.
|
||||
*/
|
||||
export interface CreateApplicationMethod {
|
||||
type: "fixed" | "percentage"
|
||||
target_type: "order" | "shipping_methods" | "items"
|
||||
allocation?: "each" | "across"
|
||||
/**
|
||||
* The application method's value.
|
||||
*/
|
||||
value?: number
|
||||
/**
|
||||
* The application method's max quantity.
|
||||
*/
|
||||
max_quantity?: number
|
||||
/**
|
||||
* The application method's buy rules min quantity.
|
||||
*/
|
||||
buy_rules_min_quantity?: number
|
||||
/**
|
||||
* The application method's apply to quantity.
|
||||
*/
|
||||
apply_to_quantity?: number
|
||||
promotion?:
|
||||
| string
|
||||
| {
|
||||
/**
|
||||
* The promotion's ID.
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* The promotion's code.
|
||||
*/
|
||||
code?: string
|
||||
type?: "standard" | "buyget"
|
||||
/**
|
||||
* The promotion's is automatic.
|
||||
*/
|
||||
is_automatic?: boolean
|
||||
application_method?: ApplicationMethod
|
||||
/**
|
||||
* The promotion's rules.
|
||||
*/
|
||||
rules?: Array<any>
|
||||
campaign?: Campaign
|
||||
}
|
||||
/**
|
||||
* The application method's target rules.
|
||||
*/
|
||||
target_rules?: Array<{
|
||||
/**
|
||||
* The target rule's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The target rule's attribute.
|
||||
*/
|
||||
attribute: string
|
||||
operator: "gt" | "lt" | "eq" | "ne" | "in" | "lte" | "gte"
|
||||
values: string | Array<string>
|
||||
}>
|
||||
/**
|
||||
* The application method's buy rules.
|
||||
*/
|
||||
buy_rules?: Array<{
|
||||
/**
|
||||
* The buy rule's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The buy rule's attribute.
|
||||
*/
|
||||
attribute: string
|
||||
operator: "gt" | "lt" | "eq" | "ne" | "in" | "lte" | "gte"
|
||||
values: string | Array<string>
|
||||
}>
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
import type { CreateCampaignBudget } from "./CreateCampaignBudget"
|
||||
|
||||
/**
|
||||
* The promotion's campaign.
|
||||
*/
|
||||
export interface CreateCampaign {
|
||||
/**
|
||||
* The campaign's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The campaign's description.
|
||||
*/
|
||||
description?: string
|
||||
/**
|
||||
* The campaign's currency.
|
||||
*/
|
||||
currency?: string
|
||||
/**
|
||||
* The campaign's campaign identifier.
|
||||
*/
|
||||
campaign_identifier: string
|
||||
/**
|
||||
* The campaign's starts at.
|
||||
*/
|
||||
starts_at: string
|
||||
/**
|
||||
* The campaign's ends at.
|
||||
*/
|
||||
ends_at: string
|
||||
budget?: CreateCampaignBudget
|
||||
/**
|
||||
* The campaign's promotions.
|
||||
*/
|
||||
promotions?: Array<{
|
||||
/**
|
||||
* The promotion's ID.
|
||||
*/
|
||||
id: string
|
||||
}>
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The campaign's budget.
|
||||
*/
|
||||
export interface CreateCampaignBudget {
|
||||
type: "spend" | "usage"
|
||||
/**
|
||||
* The budget's limit.
|
||||
*/
|
||||
limit: number
|
||||
/**
|
||||
* The budget's used.
|
||||
*/
|
||||
used?: number
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The tax region's default tax rate.
|
||||
*/
|
||||
export interface CreateDefaultTaxRate {
|
||||
/**
|
||||
* The default tax rate's rate.
|
||||
*/
|
||||
rate?: number
|
||||
/**
|
||||
* The default tax rate's code.
|
||||
*/
|
||||
code?: string
|
||||
/**
|
||||
* The default tax rate's name.
|
||||
*/
|
||||
name: string
|
||||
/**
|
||||
* The default tax rate's metadata.
|
||||
*/
|
||||
metadata?: any
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The product's type.
|
||||
*/
|
||||
export interface CreateProductType {
|
||||
/**
|
||||
* The type's ID.
|
||||
*/
|
||||
id?: string
|
||||
/**
|
||||
* The type's value.
|
||||
*/
|
||||
value: string
|
||||
/**
|
||||
* The type's metadata.
|
||||
*/
|
||||
metadata?: any
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* The stock location's address.
|
||||
*/
|
||||
export interface StockLocationAddress {
|
||||
/**
|
||||
* The address's address 1.
|
||||
*/
|
||||
address_1: string
|
||||
/**
|
||||
* The address's address 2.
|
||||
*/
|
||||
address_2?: string
|
||||
/**
|
||||
* The address's company.
|
||||
*/
|
||||
company?: string
|
||||
/**
|
||||
* The address's city.
|
||||
*/
|
||||
city?: string
|
||||
/**
|
||||
* The address's country code.
|
||||
*/
|
||||
country_code: string
|
||||
/**
|
||||
* The address's phone.
|
||||
*/
|
||||
phone?: string
|
||||
/**
|
||||
* The address's postal code.
|
||||
*/
|
||||
postal_code?: string
|
||||
/**
|
||||
* The address's province.
|
||||
*/
|
||||
province?: string
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
import { SetRelation, Merge } from "../core/ModelUtils"
|
||||
|
||||
/**
|
||||
* SUMMARY
|
||||
*/
|
||||
export interface StorePostPaymentCollectionsPaymentSessionReq {
|
||||
/**
|
||||
* The payment collection's provider id.
|
||||
*/
|
||||
provider_id: string
|
||||
context?: any
|
||||
/**
|
||||
* The payment collection's data.
|
||||
*/
|
||||
data?: any
|
||||
}
|
||||
2
packages/medusa-js/.gitignore
vendored
2
packages/medusa-js/.gitignore
vendored
@@ -1,2 +0,0 @@
|
||||
node_modules
|
||||
/dist
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +0,0 @@
|
||||
# Medusa JS Client
|
||||
|
||||
[](https://www.npmjs.org/package/@medusajs/medusa-js)
|
||||
|
||||
The Medusa JS Client provides easy access to the Medusa API from a client written in Typescript.
|
||||
|
||||
## Documentation
|
||||
|
||||
You can learn more about how to install and use this client in [our documentation](https://docs.medusajs.com/js-client/overview).
|
||||
|
||||
To learn more about the API endpoints that this client allows you to access check out our [API reference](https://docs.medusajs.com/api/store).
|
||||
@@ -1,13 +0,0 @@
|
||||
module.exports = {
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
tsconfig: "tsconfig.spec.json",
|
||||
diagnostics: false,
|
||||
isolatedModules: true,
|
||||
},
|
||||
},
|
||||
transform: {
|
||||
".(ts|tsx)$": require.resolve("ts-jest/dist/"),
|
||||
},
|
||||
testEnvironment: "jsdom",
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
{
|
||||
"name": "@medusajs/medusa-js",
|
||||
"version": "6.1.8",
|
||||
"description": "Client for Medusa Commerce Rest API",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsup src/index.ts",
|
||||
"prepare": "cross-env NODE_ENV=production yarn run build",
|
||||
"test": "jest --passWithNoTests"
|
||||
},
|
||||
"author": "Oliver Juhl",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.24.0",
|
||||
"cross-env": "^5.2.1",
|
||||
"qs": "^6.10.3",
|
||||
"retry-axios": "^2.6.0",
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/medusa": "^1.17.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/medusajs/medusa",
|
||||
"directory": "packages/medusa-js"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/medusajs/medusa/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@medusajs/medusa": "^1.20.3",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"jest": "^27.4.7",
|
||||
"ts-jest": "^27.1.5",
|
||||
"tsup": "6.7.0",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"gitHead": "cd1f5afa5aa8c0b15ea957008ee19f1d695cbd2e",
|
||||
"homepage": "https://docs.medusajs.com/js-client/overview"
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
"use strict"
|
||||
/**
|
||||
* MedusaError is the base error for every other MedusaError
|
||||
*/
|
||||
export default class MedusaError extends Error {
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
|
||||
public static factory(type: ErrorType): MedusaError {
|
||||
switch (type) {
|
||||
case ErrorType.INVALID_REQUEST:
|
||||
return new MedusaInvalidRequestError()
|
||||
case ErrorType.AUTHENTICATION:
|
||||
return new MedusaAuthenticationError()
|
||||
case ErrorType.API:
|
||||
return new MedusaAPIError()
|
||||
case ErrorType.PERMISSION:
|
||||
return new MedusaPermissionError()
|
||||
case ErrorType.CONNECTION:
|
||||
return new MedusaConnectionError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ErrorType {
|
||||
"INVALID_REQUEST",
|
||||
"API",
|
||||
"AUTHENTICATION",
|
||||
"PERMISSION",
|
||||
"CONNECTION",
|
||||
}
|
||||
|
||||
/**
|
||||
* MedusaInvalidRequestError is raised when a request as invalid parameters.
|
||||
*/
|
||||
export class MedusaInvalidRequestError extends MedusaError {}
|
||||
|
||||
/**
|
||||
* MedusaAPIError is raised in case no other type cover the problem
|
||||
*/
|
||||
export class MedusaAPIError extends MedusaError {}
|
||||
|
||||
/**
|
||||
* MedusaAuthenticationError is raised when invalid credentials is used to connect to Medusa
|
||||
*/
|
||||
export class MedusaAuthenticationError extends MedusaError {}
|
||||
|
||||
/**
|
||||
* MedusaPermissionError is raised when attempting to access a resource without permissions
|
||||
*/
|
||||
export class MedusaPermissionError extends MedusaError {}
|
||||
|
||||
/**
|
||||
* MedusaConnectionError is raised when the Medusa servers can't be reached.
|
||||
*/
|
||||
export class MedusaConnectionError extends MedusaError {}
|
||||
@@ -1,91 +0,0 @@
|
||||
import MedusaError from "./error"
|
||||
import KeyManager from "./key-manager"
|
||||
import Client, { type Config, type RequestOptions } from "./request"
|
||||
import {
|
||||
Admin,
|
||||
AuthResource,
|
||||
CartsResource,
|
||||
CollectionsResource,
|
||||
CustomersResource,
|
||||
GiftCardsResource,
|
||||
OrderEditsResource,
|
||||
OrdersResource,
|
||||
PaymentCollectionsResource,
|
||||
PaymentMethodsResource,
|
||||
ProductCategoriesResource,
|
||||
ProductsResource,
|
||||
ProductTagsResource,
|
||||
ProductTypesResource,
|
||||
RegionsResource,
|
||||
ReturnReasonsResource,
|
||||
ReturnsResource,
|
||||
ShippingOptionsResource,
|
||||
SwapsResource,
|
||||
} from "./resources"
|
||||
|
||||
class Medusa {
|
||||
public client: Client
|
||||
public admin: Admin
|
||||
|
||||
public auth: AuthResource
|
||||
public carts: CartsResource
|
||||
public customers: CustomersResource
|
||||
public errors: MedusaError
|
||||
public orders: OrdersResource
|
||||
public orderEdits: OrderEditsResource
|
||||
public products: ProductsResource
|
||||
public productTypes: ProductTypesResource
|
||||
public regions: RegionsResource
|
||||
public returnReasons: ReturnReasonsResource
|
||||
public returns: ReturnsResource
|
||||
public shippingOptions: ShippingOptionsResource
|
||||
public swaps: SwapsResource
|
||||
public collections: CollectionsResource
|
||||
public giftCards: GiftCardsResource
|
||||
public paymentMethods: PaymentMethodsResource
|
||||
public paymentCollections: PaymentCollectionsResource
|
||||
public productTags: ProductTagsResource
|
||||
public productCategories: ProductCategoriesResource
|
||||
|
||||
constructor(config: Config) {
|
||||
this.client = new Client(config)
|
||||
|
||||
this.admin = new Admin(this.client)
|
||||
|
||||
this.auth = new AuthResource(this.client)
|
||||
this.carts = new CartsResource(this.client)
|
||||
this.customers = new CustomersResource(this.client)
|
||||
this.errors = new MedusaError()
|
||||
this.orders = new OrdersResource(this.client)
|
||||
this.orderEdits = new OrderEditsResource(this.client)
|
||||
this.products = new ProductsResource(this.client)
|
||||
this.productTypes = new ProductTypesResource(this.client)
|
||||
this.regions = new RegionsResource(this.client)
|
||||
this.returnReasons = new ReturnReasonsResource(this.client)
|
||||
this.returns = new ReturnsResource(this.client)
|
||||
this.shippingOptions = new ShippingOptionsResource(this.client)
|
||||
this.swaps = new SwapsResource(this.client)
|
||||
this.collections = new CollectionsResource(this.client)
|
||||
this.giftCards = new GiftCardsResource(this.client)
|
||||
this.paymentMethods = new PaymentMethodsResource(this.client)
|
||||
this.paymentCollections = new PaymentCollectionsResource(this.client)
|
||||
this.productTags = new ProductTagsResource(this.client)
|
||||
this.productCategories = new ProductCategoriesResource(this.client)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a PublishableApiKey that will be sent with each request
|
||||
* to define the scope of available resources.
|
||||
*
|
||||
* @param key - PublishableApiKey identifier
|
||||
*/
|
||||
setPublishableKey(key: string) {
|
||||
KeyManager.registerPublishableApiKey(key)
|
||||
}
|
||||
}
|
||||
|
||||
export default Medusa
|
||||
export * from "./resources"
|
||||
export * from "./typings"
|
||||
export type { Config, RequestOptions }
|
||||
export { MedusaError, KeyManager, Client }
|
||||
@@ -1,38 +0,0 @@
|
||||
/**
|
||||
* `JwtTokenManager` holds JWT tokens in state.
|
||||
*/
|
||||
class JwtTokenManager {
|
||||
private adminJwt: string | null = null;
|
||||
private storeJwt: string | null = null;
|
||||
|
||||
/**
|
||||
* Set a store or admin jwt token to be sent with each request.
|
||||
*/
|
||||
public registerJwt(token: string, domain: "admin" | "store") {
|
||||
if (domain === "admin") {
|
||||
this.adminJwt = token;
|
||||
} else if (domain === "store") {
|
||||
this.storeJwt = token;
|
||||
} else {
|
||||
throw new Error(`'domain' must be wither 'admin' or 'store' received ${domain}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the store or admin jwt token
|
||||
*/
|
||||
public getJwt(domain: "admin" | "store") {
|
||||
if (domain === "admin") {
|
||||
return this.adminJwt;
|
||||
} else if (domain === "store") {
|
||||
return this.storeJwt;
|
||||
} else {
|
||||
throw new Error(`'domain' must be wither 'admin' or 'store' received ${domain}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export singleton instance.
|
||||
*/
|
||||
export default new JwtTokenManager()
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* `KeyManager` holds API keys in state.
|
||||
*/
|
||||
class KeyManager {
|
||||
private publishableApiKey: string | null = null
|
||||
|
||||
/**
|
||||
* Set a publishable api key to be sent with each request.
|
||||
*/
|
||||
public registerPublishableApiKey(key: string) {
|
||||
this.publishableApiKey = key
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the publishable api key.
|
||||
*/
|
||||
public getPublishableApiKey() {
|
||||
return this.publishableApiKey
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export singleton instance.
|
||||
*/
|
||||
export default new KeyManager()
|
||||
@@ -1,254 +0,0 @@
|
||||
import axios, {
|
||||
AxiosAdapter,
|
||||
AxiosError,
|
||||
AxiosInstance,
|
||||
AxiosRequestHeaders,
|
||||
} from "axios"
|
||||
import * as rax from "retry-axios"
|
||||
import { v4 as uuidv4 } from "uuid"
|
||||
|
||||
import KeyManager from "./key-manager"
|
||||
import JwtTokenManager from "./jwt-token-manager"
|
||||
|
||||
const unAuthenticatedAdminEndpoints = {
|
||||
"/admin/auth": "POST",
|
||||
"/admin/users/password-token": "POST",
|
||||
"/admin/users/reset-password": "POST",
|
||||
"/admin/invites/accept": "POST",
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
baseUrl: string
|
||||
maxRetries: number
|
||||
apiKey?: string
|
||||
publishableApiKey?: string
|
||||
customHeaders?: Record<string, any>
|
||||
axiosAdapter?: AxiosAdapter
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*
|
||||
* Options to pass to requests sent to custom API Routes
|
||||
*/
|
||||
export interface RequestOptions {
|
||||
/**
|
||||
* The number of milliseconds before the request times out.
|
||||
*/
|
||||
timeout?: number
|
||||
/**
|
||||
* The number of times to retry a request before failing.
|
||||
*/
|
||||
numberOfRetries?: number
|
||||
}
|
||||
|
||||
export type RequestMethod = "DELETE" | "POST" | "GET"
|
||||
|
||||
const defaultConfig = {
|
||||
maxRetries: 0,
|
||||
baseUrl: "http://localhost:9000",
|
||||
}
|
||||
|
||||
class Client {
|
||||
private axiosClient: AxiosInstance
|
||||
private config: Config
|
||||
|
||||
constructor(config: Config) {
|
||||
/** @private @constant {AxiosInstance} */
|
||||
this.axiosClient = this.createClient({ ...defaultConfig, ...config })
|
||||
|
||||
/** @private @constant {Config} */
|
||||
this.config = { ...defaultConfig, ...config }
|
||||
}
|
||||
|
||||
shouldRetryCondition(
|
||||
err: AxiosError,
|
||||
numRetries: number,
|
||||
maxRetries: number
|
||||
): boolean {
|
||||
// Obviously, if we have reached max. retries we stop
|
||||
if (numRetries >= maxRetries) {
|
||||
return false
|
||||
}
|
||||
|
||||
// If no response, we assume a connection error and retry
|
||||
if (!err.response) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Retry on conflicts
|
||||
if (err.response.status === 409) {
|
||||
return true
|
||||
}
|
||||
|
||||
// All 5xx errors are retried
|
||||
// OBS: We are currently not retrying 500 requests, since our core needs proper error handling.
|
||||
// At the moment, 500 will be returned on all errors, that are not of type MedusaError.
|
||||
if (err.response.status > 500 && err.response.status <= 599) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Stolen from https://github.com/stripe/stripe-node/blob/fd0a597064289b8c82f374f4747d634050739043/lib/utils.js#L282
|
||||
normalizeHeaders(obj: object): Record<string, any> {
|
||||
if (!(obj && typeof obj === "object")) {
|
||||
return obj
|
||||
}
|
||||
|
||||
return Object.keys(obj).reduce((result, header) => {
|
||||
result[this.normalizeHeader(header)] = obj[header]
|
||||
return result
|
||||
}, {})
|
||||
}
|
||||
|
||||
// Stolen from https://github.com/marten-de-vries/header-case-normalizer/blob/master/index.js#L36-L41
|
||||
normalizeHeader(header: string): string {
|
||||
return header
|
||||
.split("-")
|
||||
.map(
|
||||
(text) => text.charAt(0).toUpperCase() + text.substr(1).toLowerCase()
|
||||
)
|
||||
.join("-")
|
||||
}
|
||||
|
||||
requiresAuthentication(path, method): boolean {
|
||||
return (
|
||||
path.startsWith("/admin") &&
|
||||
unAuthenticatedAdminEndpoints[path] !== method
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates all the initial headers.
|
||||
* We add the idempotency key, if the request is configured to retry.
|
||||
* @param {object} userHeaders user supplied headers
|
||||
* @param {Types.RequestMethod} method request method
|
||||
* @param {string} path request path
|
||||
* @param {object} customHeaders user supplied headers
|
||||
* @return {object}
|
||||
*/
|
||||
setHeaders(
|
||||
userHeaders: RequestOptions,
|
||||
method: RequestMethod,
|
||||
path: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): AxiosRequestHeaders {
|
||||
let defaultHeaders: Record<string, any> = {
|
||||
Accept: "application/json",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
|
||||
if (this.config.apiKey && this.requiresAuthentication(path, method)) {
|
||||
defaultHeaders = {
|
||||
...defaultHeaders,
|
||||
"x-medusa-access-token": this.config.apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
const domain: "admin" | "store" = path.includes("admin") ? "admin" : "store"
|
||||
|
||||
if (JwtTokenManager.getJwt(domain)) {
|
||||
defaultHeaders = {
|
||||
...defaultHeaders,
|
||||
Authorization: `Bearer ${JwtTokenManager.getJwt(domain)}`,
|
||||
}
|
||||
}
|
||||
|
||||
const publishableApiKey =
|
||||
this.config.publishableApiKey || KeyManager.getPublishableApiKey()
|
||||
|
||||
if (publishableApiKey) {
|
||||
defaultHeaders["x-publishable-api-key"] = publishableApiKey
|
||||
}
|
||||
|
||||
// only add idempotency key, if we want to retry
|
||||
if (this.config.maxRetries > 0 && method === "POST") {
|
||||
defaultHeaders["Idempotency-Key"] = uuidv4()
|
||||
}
|
||||
|
||||
return Object.assign(
|
||||
{},
|
||||
defaultHeaders,
|
||||
this.normalizeHeaders(userHeaders),
|
||||
customHeaders
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the axios client used for requests
|
||||
* As part of the creation, we configure the retry conditions
|
||||
* and the exponential backoff approach.
|
||||
* @param {Config} config user supplied configurations
|
||||
* @return {AxiosInstance}
|
||||
*/
|
||||
createClient(config: Config): AxiosInstance {
|
||||
const client = axios.create({
|
||||
baseURL: config.baseUrl,
|
||||
adapter: config.axiosAdapter,
|
||||
})
|
||||
|
||||
rax.attach(client)
|
||||
|
||||
client.defaults.raxConfig = {
|
||||
instance: client,
|
||||
retry: config.maxRetries,
|
||||
backoffType: "exponential",
|
||||
shouldRetry: (err: AxiosError): boolean => {
|
||||
const cfg = rax.getConfig(err)
|
||||
if (cfg) {
|
||||
return this.shouldRetryCondition(
|
||||
err,
|
||||
cfg.currentRetryAttempt ?? 1,
|
||||
cfg.retry ?? 3
|
||||
)
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
/**
|
||||
* Axios request
|
||||
* @param method request method
|
||||
* @param path request path
|
||||
* @param payload request payload
|
||||
* @param options axios configuration
|
||||
* @param customHeaders custom request headers
|
||||
* @return
|
||||
*/
|
||||
async request(
|
||||
method: RequestMethod,
|
||||
path: string,
|
||||
payload: Record<string, any> = {},
|
||||
options: RequestOptions = {},
|
||||
customHeaders: Record<string, any> = {}
|
||||
): Promise<any> {
|
||||
|
||||
customHeaders = { ...this.config.customHeaders, ...customHeaders }
|
||||
|
||||
const reqOpts = {
|
||||
method,
|
||||
withCredentials: true,
|
||||
url: path,
|
||||
json: true,
|
||||
headers: this.setHeaders(options, method, path, customHeaders),
|
||||
}
|
||||
|
||||
if (["POST", "DELETE"].includes(method)) {
|
||||
reqOpts["data"] = payload
|
||||
}
|
||||
|
||||
// e.g. data = { cart: { ... } }, response = { status, headers, ... }
|
||||
const { data, ...response } = await this.axiosClient(reqOpts)
|
||||
|
||||
// e.g. would return an object like of this shape { cart, response }
|
||||
return { ...data, response }
|
||||
}
|
||||
}
|
||||
|
||||
export default Client
|
||||
@@ -1,102 +0,0 @@
|
||||
import {
|
||||
StoreCustomersRes,
|
||||
StorePostCustomersCustomerAddressesAddressReq,
|
||||
StorePostCustomersCustomerAddressesReq,
|
||||
} from "@medusajs/medusa"
|
||||
import { ResponsePromise } from "../typings"
|
||||
import BaseResource from "./base"
|
||||
|
||||
/**
|
||||
* This class is used to send requests to Address API Routes part of the [Store Customer API Routes](https://docs.medusajs.com/api/store#customers_postcustomers). All its method
|
||||
* are available in the JS Client under the `medusa.customers.addresses` property.
|
||||
*
|
||||
* All methods in this class require {@link AuthResource.authenticate | customer authentication}.
|
||||
*/
|
||||
class AddressesResource extends BaseResource {
|
||||
/**
|
||||
* Add an address to the logged-in customer's saved addresses.
|
||||
* @param {StorePostCustomersCustomerAddressesReq} payload - The address to add.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<StoreCustomersRes>} Resolves to the customer's details, including the customer's addresses in the `shipping_addresses` attribute.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged
|
||||
* medusa.customers.addresses.addAddress({
|
||||
* address: {
|
||||
* first_name: "Celia",
|
||||
* last_name: "Schumm",
|
||||
* address_1: "225 Bednar Curve",
|
||||
* city: "Danielville",
|
||||
* country_code: "US",
|
||||
* postal_code: "85137",
|
||||
* phone: "981-596-6748 x90188",
|
||||
* company: "Wyman LLC",
|
||||
* province: "Georgia",
|
||||
* }
|
||||
* })
|
||||
* .then(({ customer }) => {
|
||||
* console.log(customer.id);
|
||||
* })
|
||||
*/
|
||||
addAddress(
|
||||
payload: StorePostCustomersCustomerAddressesReq,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<StoreCustomersRes> {
|
||||
const path = `/store/customers/me/addresses`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an address of the logged-in customer.
|
||||
* @param {string} address_id - The ID of the address to delete.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<StoreCustomersRes>} Resolves to the customer's details, including the customer's addresses in the `shipping_addresses` attribute.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged
|
||||
* medusa.customers.addresses.deleteAddress(addressId)
|
||||
* .then(({ customer }) => {
|
||||
* console.log(customer.id);
|
||||
* })
|
||||
*/
|
||||
deleteAddress(
|
||||
address_id: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<StoreCustomersRes> {
|
||||
const path = `/store/customers/me/addresses/${address_id}`
|
||||
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an address of the logged-in customer.
|
||||
* @param {string} address_id - The address's ID.
|
||||
* @param {StorePostCustomersCustomerAddressesAddressReq} payload - The attributes to update in the address.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<StoreCustomersRes>} Resolves to the customer's details, including the customer's addresses in the `shipping_addresses` attribute.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged
|
||||
* medusa.customers.addresses.updateAddress(addressId, {
|
||||
* first_name: "Gina"
|
||||
* })
|
||||
* .then(({ customer }) => {
|
||||
* console.log(customer.id);
|
||||
* })
|
||||
*/
|
||||
updateAddress(
|
||||
address_id: string,
|
||||
payload: StorePostCustomersCustomerAddressesAddressReq,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<StoreCustomersRes> {
|
||||
const path = `/store/customers/me/addresses/${address_id}`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
export default AddressesResource
|
||||
@@ -1,114 +0,0 @@
|
||||
import { AdminAuthRes, AdminPostAuthReq, AdminBearerAuthRes } from "@medusajs/medusa"
|
||||
import { ResponsePromise } from "../../typings"
|
||||
import JwtTokenManager from "../../jwt-token-manager"
|
||||
import BaseResource from "../base"
|
||||
|
||||
/**
|
||||
* This class is used to send requests to [Admin Auth API Routes](https://docs.medusajs.com/api/admin#auth_getauth). All its method
|
||||
* are available in the JS Client under the `medusa.admin.auth` property.
|
||||
*
|
||||
* The methods in this class allow admin users to manage their session, such as login or log out.
|
||||
* You can send authenticated requests for an admin user either using the Cookie header, their API token, or the JWT Token.
|
||||
* When you log the admin user in using the {@link createSession} method, the JS client will automatically attach the
|
||||
* cookie header in all subsequent requests.
|
||||
*
|
||||
* Related Guide: [How to implement user profiles](https://docs.medusajs.com/modules/users/admin/manage-profile).
|
||||
*/
|
||||
class AdminAuthResource extends BaseResource {
|
||||
/**
|
||||
* Get the currently logged in user's details. Can also be used to check if there is an authenticated user.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminAuthRes>} Resolves to the logged-in user's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.auth.getSession()
|
||||
* .then(({ user }) => {
|
||||
* console.log(user.id);
|
||||
* })
|
||||
*/
|
||||
getSession(
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminAuthRes> {
|
||||
const path = `/admin/auth`
|
||||
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out the user and remove their authentication session. This will only work if you're using Cookie session for authentication. If the API token is still passed in the header,
|
||||
* the user is still authorized to perform admin functionalities in other API Routes.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<void>} Resolves when user is logged out successfully.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in
|
||||
* medusa.admin.auth.deleteSession()
|
||||
*/
|
||||
deleteSession(
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<void> {
|
||||
const path = `/admin/auth`
|
||||
return this.client.request("DELETE", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a User in using their credentials. If the user is authenticated successfully, the cookie is automatically attached to subsequent requests sent with the JS Client.
|
||||
* @param {AdminPostAuthReq} payload - The credentials of the user.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminAuthRes>} Resolves to the user's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* medusa.admin.AdminAuthResource.createSession({
|
||||
* email: "user@example.com",
|
||||
* password: "supersecret"
|
||||
* })
|
||||
* .then(({ user }) => {
|
||||
* console.log(user.id);
|
||||
* })
|
||||
*/
|
||||
createSession(
|
||||
payload: AdminPostAuthReq,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminAuthRes> {
|
||||
const path = `/admin/auth`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate the user and retrieve a JWT token to use for subsequent authenticated requests.
|
||||
* @param {AdminPostAuthReq} payload - The credentials of the user.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBearerAuthRes>} Resolves to the access token of the user, if they're authenticated successfully.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* medusa.admin.auth.getToken({
|
||||
* email: 'user@example.com',
|
||||
* password: 'supersecret'
|
||||
* })
|
||||
* .then(({ access_token }) => {
|
||||
* console.log(access_token);
|
||||
* })
|
||||
*/
|
||||
getToken(
|
||||
payload: AdminPostAuthReq,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBearerAuthRes> {
|
||||
const path = `/admin/auth/token`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
.then((res) => {
|
||||
JwtTokenManager.registerJwt(res.access_token, "admin");
|
||||
|
||||
return res
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminAuthResource
|
||||
@@ -1,185 +0,0 @@
|
||||
import {
|
||||
AdminBatchJobListRes,
|
||||
AdminBatchJobRes,
|
||||
AdminGetBatchParams,
|
||||
AdminPostBatchesReq,
|
||||
} from "@medusajs/medusa"
|
||||
import qs from "qs"
|
||||
import { ResponsePromise } from "../../typings"
|
||||
import BaseResource from "../base"
|
||||
import { stringifyNullProperties } from "../../utils"
|
||||
|
||||
/**
|
||||
* This class is used to send requests to [Admin Batch Job API Routes](https://docs.medusajs.com/api/admin#batch-jobs). All its method
|
||||
* are available in the JS Client under the `medusa.admin.batchJobs` property.
|
||||
*
|
||||
* All methods in this class require {@link AdminAuthResource.createSession | user authentication}.
|
||||
*
|
||||
* A batch job is a task that is performed by the Medusa backend asynchronusly. For example, the Import Product feature is implemented using batch jobs.
|
||||
* The methods in this class allow admins to manage the batch jobs and their state.
|
||||
*
|
||||
* Related Guide: [How to import products](https://docs.medusajs.com/modules/products/admin/import-products).
|
||||
*/
|
||||
class AdminBatchJobsResource extends BaseResource {
|
||||
|
||||
/**
|
||||
* Create a Batch Job to be executed asynchronously in the Medusa backend. If `dry_run` is set to `true`, the batch job will not be executed until the it is confirmed,
|
||||
* which can be done using the {@link confirm} method.
|
||||
* @param payload - The data of the batch job to create.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBatchJobRes>} Resolves to the batch job's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.create({
|
||||
* type: 'product-export',
|
||||
* context: {},
|
||||
* dry_run: false
|
||||
* }).then((({ batch_job }) => {
|
||||
* console.log(batch_job.id);
|
||||
* })
|
||||
*/
|
||||
create(
|
||||
payload: AdminPostBatchesReq,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBatchJobRes> {
|
||||
const path = `/admin/batch-jobs`
|
||||
return this.client.request("POST", path, payload, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve a list of Batch Jobs. The batch jobs can be filtered by fields such as `type` or `confirmed_at`. The batch jobs can also be sorted or paginated.
|
||||
* @param {AdminGetBatchParams} query - Filters and pagination configurations to apply on the retrieved batch jobs.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBatchJobListRes>} The list of batch jobs with pagination fields.
|
||||
*
|
||||
* @example
|
||||
* To list batch jobs:
|
||||
*
|
||||
* ```ts
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.list()
|
||||
* .then(({ batch_jobs, limit, offset, count }) => {
|
||||
* console.log(batch_jobs.length)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* To specify relations that should be retrieved within the batch jobs:
|
||||
*
|
||||
* ```ts
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.list({
|
||||
* expand: "created_by_user"
|
||||
* })
|
||||
* .then(({ batch_jobs, limit, offset, count }) => {
|
||||
* console.log(batch_jobs.length)
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* By default, only the first `10` records are retrieved. You can control pagination by specifying the `limit` and `offset` properties:
|
||||
*
|
||||
* ```ts
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.list({
|
||||
* expand: "created_by_user",
|
||||
* limit,
|
||||
* offset
|
||||
* })
|
||||
* .then(({ batch_jobs, limit, offset, count }) => {
|
||||
* console.log(batch_jobs.length)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
list(
|
||||
query?: AdminGetBatchParams,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBatchJobListRes> {
|
||||
let path = `/admin/batch-jobs`
|
||||
|
||||
if (query) {
|
||||
const queryString = qs.stringify(stringifyNullProperties(query))
|
||||
path = `/admin/batch-jobs?${queryString}`
|
||||
}
|
||||
|
||||
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a batch job as canceled. When a batch job is canceled, the processing of the batch job doesn’t automatically stop.
|
||||
* @param {string} batchJobId - The ID of the batch job.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBatchJobRes>} Resolves to the batch job's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.cancel(batchJobId)
|
||||
* .then(({ batch_job }) => {
|
||||
* console.log(batch_job.id);
|
||||
* })
|
||||
*/
|
||||
cancel(
|
||||
batchJobId: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBatchJobRes> {
|
||||
const path = `/admin/batch-jobs/${batchJobId}/cancel`
|
||||
return this.client.request("POST", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* When a batch job is created, it's not executed automatically if `dry_run` is set to `true`. This method confirms that the batch job should be executed.
|
||||
* @param {string} batchJobId - The ID of the batch job.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBatchJobRes>} Resolves to the batch job's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.confirm(batchJobId)
|
||||
* .then(({ batch_job }) => {
|
||||
* console.log(batch_job.id);
|
||||
* })
|
||||
*/
|
||||
confirm(
|
||||
batchJobId: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBatchJobRes> {
|
||||
const path = `/admin/batch-jobs/${batchJobId}/confirm`
|
||||
return this.client.request("POST", path, undefined, {}, customHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the details of a batch job.
|
||||
* @param {string} batchJobId - The ID of the batch job.
|
||||
* @param {Record<string, any>} customHeaders - Custom headers to attach to the request.
|
||||
* @returns {ResponsePromise<AdminBatchJobRes>} Resolves to the batch job's details.
|
||||
*
|
||||
* @example
|
||||
* import Medusa from "@medusajs/medusa-js"
|
||||
* const medusa = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 })
|
||||
* // must be previously logged in or use api token
|
||||
* medusa.admin.batchJobs.retrieve(batchJobId)
|
||||
* .then(({ batch_job }) => {
|
||||
* console.log(batch_job.id);
|
||||
* })
|
||||
*/
|
||||
retrieve(
|
||||
batchJobId: string,
|
||||
customHeaders: Record<string, any> = {}
|
||||
): ResponsePromise<AdminBatchJobRes> {
|
||||
const path = `/admin/batch-jobs/${batchJobId}`
|
||||
return this.client.request("GET", path, undefined, {}, customHeaders)
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminBatchJobsResource
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user