chore(medusa): cleanup medusa package (#7206)

This commit is contained in:
Adrien de Peretti
2024-05-07 12:48:49 +02:00
committed by GitHub
parent 8b61dccd0f
commit 71f4f46cb9
1452 changed files with 4737 additions and 234780 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
# Default postgres credentials
DB_HOST=localhost
DB_USERNAME=postgres
DB_PASSWORD=''
DB_NAME=development
SERVER_PORT=9000

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +0,0 @@
import { workflowEngineTestSuite } from "./tests"
jest.setTimeout(5000000)
const env = {
MEDUSA_FF_MEDUSA_V2: false,
}
workflowEngineTestSuite(env, { force_modules_migration: true })

View File

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

View File

@@ -1,4 +0,0 @@
dist/
node_modules
*yarn-error.log

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,9 @@
/* istanbul ignore file */
/* tslint:disable */
/* eslint-disable */
import { SetRelation, Merge } from "../core/ModelUtils"
/**
* The promotion's campaign.
*/
export interface Campaign {}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
node_modules
/dist

File diff suppressed because one or more lines are too long

View File

@@ -1,11 +0,0 @@
# Medusa JS Client
[![Version](https://img.shields.io/npm/v/stripe.svg)](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).

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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