chore(medusa): cleanup medusa package (#7206)
This commit is contained in:
committed by
GitHub
parent
8b61dccd0f
commit
71f4f46cb9
@@ -9,9 +9,7 @@
|
|||||||
"updateInternalDependencies": "patch",
|
"updateInternalDependencies": "patch",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"integration-tests-api",
|
"integration-tests-api",
|
||||||
"integration-tests-plugins",
|
|
||||||
"integration-tests-modules",
|
"integration-tests-modules",
|
||||||
"integration-tests-repositories",
|
|
||||||
"@medusajs/dashboard",
|
"@medusajs/dashboard",
|
||||||
"@medusajs/admin-shared",
|
"@medusajs/admin-shared",
|
||||||
"@medusajs/admin-bundler",
|
"@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:
|
with:
|
||||||
path: |
|
path: |
|
||||||
.yarn/cache
|
.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: |
|
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.
|
# We want to only bootstrap and install if no cache is found.
|
||||||
- run: yarn install --immutable
|
- run: yarn install --immutable
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
225
.github/workflows/action.yml
vendored
225
.github/workflows/action.yml
vendored
@@ -2,12 +2,12 @@ name: Medusa Pipeline
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
- v1.x
|
- v1.x
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- develop
|
- develop
|
||||||
- v1.x
|
- v1.x
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
setup:
|
setup:
|
||||||
@@ -157,75 +157,75 @@ jobs:
|
|||||||
DB_PASSWORD: postgres
|
DB_PASSWORD: postgres
|
||||||
DB_USERNAME: postgres
|
DB_USERNAME: postgres
|
||||||
|
|
||||||
integration-tests-api-matrix:
|
#integration-tests-api-matrix:
|
||||||
needs: setup
|
# needs: setup
|
||||||
name: Shard (${{ matrix.chunk }}) API Integration Tests
|
# name: Shard (${{ matrix.chunk }}) API Integration Tests
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
strategy:
|
# strategy:
|
||||||
fail-fast: false
|
# fail-fast: false
|
||||||
matrix:
|
# matrix:
|
||||||
chunk: ${{ fromJSON(needs.setup.outputs.api-matrix) }}
|
# chunk: ${{ fromJSON(needs.setup.outputs.api-matrix) }}
|
||||||
env:
|
# env:
|
||||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
# 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:
|
# steps:
|
||||||
redis:
|
# - name: Checkout
|
||||||
image: redis
|
# uses: actions/checkout@v3
|
||||||
options: >-
|
# with:
|
||||||
--health-cmd "redis-cli ping"
|
# fetch-depth: 0
|
||||||
--health-interval 1s
|
#
|
||||||
--health-timeout 10s
|
# - name: Install dependencies
|
||||||
--health-retries 10
|
# uses: ./.github/actions/cache-deps
|
||||||
ports:
|
# with:
|
||||||
- 6379:6379
|
# extension: pipeline
|
||||||
postgres:
|
#
|
||||||
image: postgres
|
# - name: Run API integration tests
|
||||||
env:
|
# run: yarn test:integration:api
|
||||||
POSTGRES_PASSWORD: postgres
|
# env:
|
||||||
POSTGRES_USER: postgres
|
# DB_USERNAME: postgres
|
||||||
options: >-
|
# DB_PASSWORD: postgres
|
||||||
--health-cmd pg_isready
|
# NODE_OPTIONS: "--max_old_space_size=4096"
|
||||||
--health-interval 1s
|
# CHUNK: ${{ matrix.chunk }}
|
||||||
--health-timeout 10s
|
# CHUNKS: ${{ needs.setup.outputs.api-chunks }}
|
||||||
--health-retries 10
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
|
|
||||||
steps:
|
#integration-tests-api:
|
||||||
- name: Checkout
|
# if: ${{ always() }}
|
||||||
uses: actions/checkout@v3
|
# runs-on: ubuntu-latest
|
||||||
with:
|
# needs: integration-tests-api-matrix
|
||||||
fetch-depth: 0
|
# steps:
|
||||||
|
# - run: exit 1
|
||||||
- name: Install dependencies
|
# if: >-
|
||||||
uses: ./.github/actions/cache-deps
|
# ${{
|
||||||
with:
|
# contains(needs.integration-tests-api-matrix.result, 'failure')
|
||||||
extension: pipeline
|
# || contains(needs.integration-tests-api-matrix.result, 'cancelled')
|
||||||
|
# || contains(needs.integration-tests-api-matrix.result, 'skipped')
|
||||||
- name: Run API integration tests
|
# }}
|
||||||
run: yarn test:integration:api
|
# - run: exit 0
|
||||||
env:
|
# if: ${{ contains(needs.integration-tests-api-matrix.result, 'success') }}
|
||||||
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') }}
|
|
||||||
|
|
||||||
unit-tests:
|
unit-tests:
|
||||||
if: ${{ always() }}
|
if: ${{ always() }}
|
||||||
@@ -242,51 +242,6 @@ jobs:
|
|||||||
- run: exit 0
|
- run: exit 0
|
||||||
if: ${{ contains(needs.unit-tests-matrix.result, 'success') }}
|
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:
|
integration-tests-modules-matrix:
|
||||||
needs: setup
|
needs: setup
|
||||||
name: Shard (${{ matrix.chunk }}) Module Integration Tests
|
name: Shard (${{ matrix.chunk }}) Module Integration Tests
|
||||||
@@ -346,41 +301,3 @@ jobs:
|
|||||||
}}
|
}}
|
||||||
- run: exit 0
|
- run: exit 0
|
||||||
if: ${{ contains(needs.integration-tests-modules-matrix.result, 'success') }}
|
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
|
#name: CLI Pipeline
|
||||||
on:
|
#on:
|
||||||
pull_request:
|
# pull_request:
|
||||||
|
#
|
||||||
jobs:
|
#jobs:
|
||||||
test-cli-with-database:
|
# test-cli-with-database:
|
||||||
env:
|
# env:
|
||||||
NODE_ENV: CI
|
# NODE_ENV: CI
|
||||||
REDIS_URL: redis://localhost:6379
|
# REDIS_URL: redis://localhost:6379
|
||||||
DATABASE_URL: "postgres://postgres:postgres@localhost/cli-test"
|
# DATABASE_URL: "postgres://postgres:postgres@localhost/cli-test"
|
||||||
services:
|
# services:
|
||||||
redis:
|
# redis:
|
||||||
image: redis
|
# image: redis
|
||||||
# Set health checks to wait until redis has started
|
# Set health checks to wait until redis has started
|
||||||
options: >-
|
# options: >-
|
||||||
--health-cmd "redis-cli ping"
|
# --health-cmd "redis-cli ping"
|
||||||
--health-interval 10s
|
# --health-interval 10s
|
||||||
--health-timeout 5s
|
# --health-timeout 5s
|
||||||
--health-retries 5
|
# --health-retries 5
|
||||||
ports:
|
# ports:
|
||||||
- 6379:6379
|
# - 6379:6379
|
||||||
|
#
|
||||||
postgres:
|
# postgres:
|
||||||
image: postgres
|
# image: postgres
|
||||||
env:
|
# env:
|
||||||
POSTGRES_PASSWORD: postgres
|
# POSTGRES_PASSWORD: postgres
|
||||||
POSTGRES_USER: postgres
|
# POSTGRES_USER: postgres
|
||||||
POSTGRES_DB: cli-test
|
# POSTGRES_DB: cli-test
|
||||||
options: >-
|
# options: >-
|
||||||
--health-cmd pg_isready
|
# --health-cmd pg_isready
|
||||||
--health-interval 10s
|
# --health-interval 10s
|
||||||
--health-timeout 5s
|
# --health-timeout 5s
|
||||||
--health-retries 5
|
# --health-retries 5
|
||||||
ports:
|
# ports:
|
||||||
- 5432:5432
|
# - 5432:5432
|
||||||
|
#
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout
|
# - name: Checkout
|
||||||
uses: actions/checkout@v3
|
# uses: actions/checkout@v3
|
||||||
with:
|
# with:
|
||||||
fetch-depth: 0
|
# fetch-depth: 0
|
||||||
|
#
|
||||||
- name: Setup development server
|
# - name: Setup development server
|
||||||
uses: ./.github/actions/setup-server
|
# uses: ./.github/actions/setup-server
|
||||||
with:
|
# with:
|
||||||
cache-extension: "cli-test"
|
# cache-extension: "cli-test"
|
||||||
node-version: "16.14"
|
# node-version: "16.14"
|
||||||
|
#
|
||||||
- name: Install Medusa cli
|
# - name: Install Medusa cli
|
||||||
run: npm i -g @medusajs/medusa-cli
|
# run: npm i -g @medusajs/medusa-cli
|
||||||
|
#
|
||||||
- name: Create Medusa project
|
# - name: Create Medusa project
|
||||||
run: |
|
# run: |
|
||||||
medusa new cli-test --skip-db
|
# medusa new cli-test --skip-db
|
||||||
working-directory: ..
|
# working-directory: ..
|
||||||
|
#
|
||||||
- name: run medusa dev
|
# - name: run medusa dev
|
||||||
run: medusa-dev --force-install
|
# run: medusa-dev --force-install
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
- name: Run migrations
|
# - name: Run migrations
|
||||||
run: medusa migrations run
|
# run: medusa migrations run
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
- name: Seed db
|
# - name: Seed db
|
||||||
run: yarn seed
|
# run: yarn seed
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
- name: Create admin user
|
# - name: Create admin user
|
||||||
run: medusa user -e test@test.com -p password -i admin_123
|
# run: medusa user -e test@test.com -p password -i admin_123
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
########################## Test medusa develop ###############################
|
# Test medusa develop
|
||||||
|
#
|
||||||
- name: Run development server
|
# - name: Run development server
|
||||||
run: medusa develop &
|
# run: medusa develop &
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
- name: Testing development server
|
# - name: Testing development server
|
||||||
uses: ./.github/actions/test-server
|
# uses: ./.github/actions/test-server
|
||||||
|
#
|
||||||
########################### Test medusa start ################################
|
# Test medusa start
|
||||||
|
#
|
||||||
- name: Starting medusa
|
# - name: Starting medusa
|
||||||
run: medusa start &
|
# run: medusa start &
|
||||||
working-directory: ../cli-test
|
# working-directory: ../cli-test
|
||||||
|
#
|
||||||
- name: Testing server
|
# - name: Testing server
|
||||||
uses: ./.github/actions/test-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"
|
} from "@medusajs/medusa"
|
||||||
import faker from "faker"
|
import faker from "faker"
|
||||||
import { DataSource } from "typeorm"
|
import { DataSource } from "typeorm"
|
||||||
import {
|
|
||||||
DiscountConditionFactoryData,
|
|
||||||
simpleDiscountConditionFactory,
|
|
||||||
} from "./simple-discount-condition-factory"
|
|
||||||
|
|
||||||
export type DiscountRuleFactoryData = {
|
export type DiscountRuleFactoryData = {
|
||||||
type?: DiscountRuleType
|
type?: DiscountRuleType
|
||||||
value?: number
|
value?: number
|
||||||
allocation?: AllocationType
|
allocation?: AllocationType
|
||||||
conditions: DiscountConditionFactoryData[]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DiscountFactoryData = {
|
export type DiscountFactoryData = {
|
||||||
@@ -48,16 +43,6 @@ export const simpleDiscountFactory = async (
|
|||||||
|
|
||||||
const dRule = await manager.save(ruleToSave)
|
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, {
|
const toSave = manager.create(Discount, {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
is_dynamic: data.is_dynamic ?? false,
|
is_dynamic: data.is_dynamic ?? false,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const Scrypt = require("scrypt-kdf")
|
const Scrypt = require("scrypt-kdf")
|
||||||
const { User } = require("@medusajs/medusa/dist/models/user")
|
|
||||||
|
|
||||||
module.exports = async (dataSource, data = {}) => {
|
module.exports = async (dataSource, data = {}) => {
|
||||||
const manager = dataSource.manager
|
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 buf = await Scrypt.kdf("secret_password", { logN: 15, r: 8, p: 1 })
|
||||||
const password_hash = buf.toString("base64")
|
const password_hash = buf.toString("base64")
|
||||||
|
|
||||||
const user = await manager.insert(User, {
|
const user = await manager.insert("user", {
|
||||||
id: "admin_user",
|
id: "admin_user",
|
||||||
email: "admin@medusa.js",
|
email: "admin@medusa.js",
|
||||||
api_token: "test_token",
|
api_token: "test_token",
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ medusaIntegrationTestRunner({
|
|||||||
price_set_id: priceSet.id,
|
price_set_id: priceSet.id,
|
||||||
shipping_option_id: shippingOption.id,
|
shipping_option_id: shippingOption.id,
|
||||||
}),
|
}),
|
||||||
prices: [
|
prices: expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
amount: 5000,
|
amount: 5000,
|
||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
@@ -127,7 +127,7 @@ medusaIntegrationTestRunner({
|
|||||||
amount: 3000,
|
amount: 3000,
|
||||||
currency_code: "usd",
|
currency_code: "usd",
|
||||||
}),
|
}),
|
||||||
],
|
]),
|
||||||
calculated_price: expect.objectContaining({
|
calculated_price: expect.objectContaining({
|
||||||
calculated_amount: 5000,
|
calculated_amount: 5000,
|
||||||
currency_code: "eur",
|
currency_code: "eur",
|
||||||
|
|||||||
@@ -548,7 +548,8 @@ medusaIntegrationTestRunner({
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe("POST /admin/price-lists/:id/prices/batch", () => {
|
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({
|
const priceSet = await createVariantPriceSet({
|
||||||
container: appContainer,
|
container: appContainer,
|
||||||
variantId: variant.id,
|
variantId: variant.id,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useApi } from "../../../../environment-helpers/use-api"
|
|||||||
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
import { initDb, useDb } from "../../../../environment-helpers/use-db"
|
||||||
|
|
||||||
import adminSeeder from "../../../../helpers/admin-seeder"
|
import adminSeeder from "../../../../helpers/admin-seeder"
|
||||||
import productSeeder from "../../../../helpers/product-seeder"
|
|
||||||
|
|
||||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||||
import { MedusaV2Flag } from "@medusajs/utils"
|
import { MedusaV2Flag } from "@medusajs/utils"
|
||||||
@@ -68,7 +67,6 @@ describe.skip("/admin/products", () => {
|
|||||||
|
|
||||||
describe("POST /admin/products", () => {
|
describe("POST /admin/products", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await productSeeder(dbConnection)
|
|
||||||
await adminSeeder(dbConnection)
|
await adminSeeder(dbConnection)
|
||||||
await createDefaultRuleTypes(medusaContainer)
|
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 glob = require(`glob`)
|
||||||
const fs = require(`fs`)
|
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 reMedusa = /medusa$/
|
||||||
const medusaDir = pkgs.find((p) => reMedusa.exec(p))
|
const medusaDir = pkgs.find((p) => reMedusa.exec(p))
|
||||||
@@ -26,7 +34,13 @@ module.exports = {
|
|||||||
notify: true,
|
notify: true,
|
||||||
verbose: true,
|
verbose: true,
|
||||||
roots: ["<rootDir>"],
|
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,
|
modulePathIgnorePatterns: ignoreDirs,
|
||||||
coveragePathIgnorePatterns: ignoreDirs,
|
coveragePathIgnorePatterns: ignoreDirs,
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -9,8 +9,6 @@
|
|||||||
"packages/core/*",
|
"packages/core/*",
|
||||||
"packages/cli/*",
|
"packages/cli/*",
|
||||||
"packages/cli/oas/*",
|
"packages/cli/oas/*",
|
||||||
"packages/medusa-js",
|
|
||||||
"packages/medusa-react",
|
|
||||||
"packages/*",
|
"packages/*",
|
||||||
"packages/admin-next/*",
|
"packages/admin-next/*",
|
||||||
"packages/design-system/*",
|
"packages/design-system/*",
|
||||||
@@ -78,15 +76,11 @@
|
|||||||
"test:chunk": "./scripts/run-workspace-unit-tests-in-chunks.sh",
|
"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: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: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: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/oas/oas-github-ci run ci --with-full-file --v2",
|
||||||
"openapi:generate": "yarn ./packages/cli/oas/oas-github-ci run ci --with-full-file",
|
"medusa-oas": "yarn ./packages/oas/medusa-oas-cli run medusa-oas --v2",
|
||||||
"medusa-oas": "yarn ./packages/cli/oas/medusa-oas-cli run medusa-oas",
|
|
||||||
"release:snapshot": "changeset publish --no-git-tags --snapshot --tag snapshot",
|
"release:snapshot": "changeset publish --no-git-tags --snapshot --tag snapshot",
|
||||||
"develop": "ts-node --transpile-only ./integration-tests/development/server.js",
|
"release:next": "chgstangeset publish --no-git-tags --snapshot --tag next",
|
||||||
"develop:create:db": "ts-node --transpile-only ./integration-tests/development/create-database.js",
|
|
||||||
"release:next": "changeset publish --no-git-tags --snapshot --tag next",
|
|
||||||
"version:next": "changeset version --snapshot next",
|
"version:next": "changeset version --snapshot next",
|
||||||
"release": "changeset publish",
|
"release": "changeset publish",
|
||||||
"version": "changeset version && yarn install --no-immutable"
|
"version": "changeset version && yarn install --no-immutable"
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
"i18next-browser-languagedetector": "7.2.0",
|
"i18next-browser-languagedetector": "7.2.0",
|
||||||
"i18next-http-backend": "2.4.2",
|
"i18next-http-backend": "2.4.2",
|
||||||
"match-sorter": "^6.3.4",
|
"match-sorter": "^6.3.4",
|
||||||
"medusa-react": "workspace:^",
|
"medusa-react": "latest",
|
||||||
"qs": "^6.12.0",
|
"qs": "^6.12.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-country-flag": "^3.1.0",
|
"react-country-flag": "^3.1.0",
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"paths": {
|
||||||
|
"@babel/types": ["../../../node_modules/@babel/types"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export const commandOptions: Option[] = [
|
|||||||
),
|
),
|
||||||
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
new Option("-F, --force", "Ignore OAS validation and output OAS files."),
|
||||||
new Option(
|
new Option(
|
||||||
"--v2",
|
"--v2",
|
||||||
"Generate OAS files for V2 endpoints. This loads OAS from www/utils/generated/oas-output/operations directory"
|
"Generate OAS files for V2 endpoints. This loads OAS from www/utils/generated/oas-output/operations directory"
|
||||||
),
|
),
|
||||||
new Option(
|
new Option(
|
||||||
@@ -151,7 +151,7 @@ async function getOASFromCodebase(
|
|||||||
): Promise<OpenAPIObject> {
|
): Promise<OpenAPIObject> {
|
||||||
/**
|
/**
|
||||||
* OAS output directory
|
* OAS output directory
|
||||||
*
|
*
|
||||||
* @privateRemark
|
* @privateRemark
|
||||||
* This should be the only directory OAS is loaded from for Medusa V2.
|
* 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.
|
* 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"),
|
path.resolve(oasOutputPath, "schemas"),
|
||||||
// We currently load error schemas from here. If we change
|
// We currently load error schemas from here. If we change
|
||||||
// that in the future, we should change the path.
|
// 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(medusaTypesPath, "dist"),
|
||||||
path.resolve(medusaUtilsPath, "dist"),
|
path.resolve(medusaUtilsPath, "dist"),
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { getDatabaseURL } from "./database"
|
import { ContainerLike, MedusaContainer } from "@medusajs/types"
|
||||||
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 { createMedusaContainer } from "@medusajs/utils"
|
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
|
const axios = require("axios").default
|
||||||
|
|
||||||
@@ -114,12 +114,16 @@ export function medusaIntegrationTestRunner({
|
|||||||
) => {
|
) => {
|
||||||
const config = originalConfigLoader(rootDirectory)
|
const config = originalConfigLoader(rootDirectory)
|
||||||
config.projectConfig.database_url = dbConfig.clientUrl
|
config.projectConfig.database_url = dbConfig.clientUrl
|
||||||
config.projectConfig.database_driver_options = dbConfig.clientUrl.includes("localhost") ? {} : {
|
config.projectConfig.database_driver_options = dbConfig.clientUrl.includes(
|
||||||
connection: {
|
"localhost"
|
||||||
ssl: { rejectUnauthorized: false },
|
)
|
||||||
},
|
? {}
|
||||||
idle_in_transaction_session_timeout: 20000,
|
: {
|
||||||
}
|
connection: {
|
||||||
|
ssl: { rejectUnauthorized: false },
|
||||||
|
},
|
||||||
|
idle_in_transaction_session_timeout: 20000,
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,19 +228,6 @@ export function medusaIntegrationTestRunner({
|
|||||||
const container = options.getContainer()
|
const container = options.getContainer()
|
||||||
const copiedContainer = createMedusaContainer({}, container)
|
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 {
|
try {
|
||||||
const medusaAppLoaderRunner =
|
const medusaAppLoaderRunner =
|
||||||
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
|
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
|
||||||
|
|||||||
@@ -82,10 +82,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@medusajs/icons": "^1.2.1",
|
"@medusajs/icons": "^1.2.1",
|
||||||
"@radix-ui/react-accordion": "^1.1.2",
|
"@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-avatar": "^1.0.3",
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
"@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-dropdown-menu": "^2.0.5",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.6",
|
"@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