feat(medusa,brightpearl,segment,webshipper): claims (#163)

* chore: create tests

* chore: models

* fix: passing initial tests

* test: adds integration test

* test: clean up integration implementation

* fix: claims

* fix: brightpearl + webshipper

* tests: passing

* fix: update claim items

* fix: adds gitignore

* fix: pr comments

* fix: single migration

* fix(medusa-plugin-segment): adds item claimed event to segment
This commit is contained in:
Sebastian Rindom
2021-02-03 09:49:12 +01:00
committed by GitHub
parent 0e0dfea571
commit 690d339667
60 changed files with 9429 additions and 189 deletions

View File

@@ -0,0 +1,13 @@
let ignore = [`**/dist`];
// Jest needs to compile this code, but generally we don't want this copied
// to output folders
if (process.env.NODE_ENV !== `test`) {
ignore.push(`**/__tests__`);
}
module.exports = {
sourceMaps: true,
presets: ["babel-preset-medusa-package"],
ignore,
};

4
integration-tests/api/.gitignore vendored Normal file
View File

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

View File

@@ -0,0 +1,469 @@
const { dropDatabase } = require("pg-god");
const path = require("path");
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb } = require("../../../helpers/use-db");
const orderSeeder = require("../../helpers/order-seeder");
const adminSeeder = require("../../helpers/admin-seeder");
jest.setTimeout(30000);
describe("/admin/orders", () => {
let medusaProcess;
let dbConnection;
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
afterAll(async () => {
await dbConnection.close();
await dropDatabase({ databaseName: "medusa-integration" });
medusaProcess.kill();
});
describe("GET /admin/orders", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await orderSeeder(dbConnection);
} catch (err) {
console.log(err);
throw err;
}
});
afterEach(async () => {
const manager = dbConnection.manager;
await manager.query(`DELETE FROM "cart"`);
await manager.query(`DELETE FROM "fulfillment"`);
await manager.query(`DELETE FROM "swap"`);
await manager.query(`DELETE FROM "return"`);
await manager.query(`DELETE FROM "claim_image"`);
await manager.query(`DELETE FROM "claim_tag"`);
await manager.query(`DELETE FROM "claim_item"`);
await manager.query(`DELETE FROM "claim_order"`);
await manager.query(`DELETE FROM "line_item"`);
await manager.query(`DELETE FROM "money_amount"`);
await manager.query(`DELETE FROM "product_variant"`);
await manager.query(`DELETE FROM "product"`);
await manager.query(`DELETE FROM "shipping_method"`);
await manager.query(`DELETE FROM "shipping_option"`);
await manager.query(`DELETE FROM "discount"`);
await manager.query(`DELETE FROM "payment"`);
await manager.query(`DELETE FROM "order"`);
await manager.query(`DELETE FROM "customer"`);
await manager.query(
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
);
await manager.query(`DELETE FROM "region"`);
await manager.query(`DELETE FROM "user"`);
});
it("creates a cart", async () => {
const api = useApi();
const response = await api
.get("/admin/orders", {
headers: {
Authorization: "Bearer test_token",
},
})
.catch((err) => {
console.log(err);
});
expect(response.status).toEqual(200);
});
});
describe("POST /admin/orders/:id/claims", () => {
beforeEach(async () => {
try {
await adminSeeder(dbConnection);
await orderSeeder(dbConnection);
} catch (err) {
console.log(err);
throw err;
}
});
afterEach(async () => {
const manager = dbConnection.manager;
await manager.query(`DELETE FROM "cart"`);
await manager.query(`DELETE FROM "fulfillment_item"`);
await manager.query(`DELETE FROM "fulfillment"`);
await manager.query(`DELETE FROM "swap"`);
await manager.query(`DELETE FROM "return"`);
await manager.query(`DELETE FROM "claim_image"`);
await manager.query(`DELETE FROM "claim_tag"`);
await manager.query(`DELETE FROM "claim_item"`);
await manager.query(`DELETE FROM "shipping_method"`);
await manager.query(`DELETE FROM "line_item"`);
await manager.query(`DELETE FROM "claim_order"`);
await manager.query(`DELETE FROM "money_amount"`);
await manager.query(`DELETE FROM "product_variant"`);
await manager.query(`DELETE FROM "product"`);
await manager.query(`DELETE FROM "shipping_option"`);
await manager.query(`DELETE FROM "discount"`);
await manager.query(`DELETE FROM "payment"`);
await manager.query(`DELETE FROM "order"`);
await manager.query(`DELETE FROM "customer"`);
await manager.query(
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
);
await manager.query(`DELETE FROM "region"`);
await manager.query(`DELETE FROM "user"`);
});
it("creates a claim", async () => {
const api = useApi();
const response = await api.post(
"/admin/orders/test-order/claims",
{
type: "replace",
claim_items: [
{
item_id: "test-item",
quantity: 1,
reason: "production_failure",
tags: ["fluff"],
images: ["https://test.image.com"],
},
],
additional_items: [
{
variant_id: "test-variant",
quantity: 1,
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
);
expect(response.status).toEqual(200);
expect(response.data.order.claims[0].claim_items).toEqual([
expect.objectContaining({
item_id: "test-item",
quantity: 1,
reason: "production_failure",
images: [
expect.objectContaining({
url: "https://test.image.com",
}),
],
tags: [
expect.objectContaining({
value: "fluff",
}),
],
}),
]);
expect(response.data.order.claims[0].additional_items).toEqual([
expect.objectContaining({
variant_id: "test-variant",
quantity: 1,
}),
]);
});
it("updates a claim", async () => {
const api = useApi();
const response = await api.post(
"/admin/orders/test-order/claims",
{
type: "replace",
claim_items: [
{
item_id: "test-item",
quantity: 1,
reason: "production_failure",
tags: ["fluff"],
images: ["https://test.image.com"],
},
],
additional_items: [
{
variant_id: "test-variant",
quantity: 1,
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
);
expect(response.status).toEqual(200);
const cid = response.data.order.claims[0].id;
const { status, data: updateData } = await api.post(
`/admin/orders/test-order/claims/${cid}`,
{
shipping_methods: [
{
id: "test-method",
},
],
},
{
headers: {
authorization: "bearer test_token",
},
}
);
expect(status).toEqual(200);
expect(updateData.order.claims[0].shipping_methods).toEqual([
expect.objectContaining({
id: "test-method",
}),
]);
});
it("updates claim items", async () => {
const api = useApi();
const response = await api.post(
"/admin/orders/test-order/claims",
{
type: "replace",
claim_items: [
{
item_id: "test-item",
quantity: 1,
reason: "production_failure",
tags: ["fluff"],
images: ["https://test.image.com"],
},
],
additional_items: [
{
variant_id: "test-variant",
quantity: 1,
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
);
expect(response.status).toEqual(200);
let claim = response.data.order.claims[0];
const cid = claim.id;
const { status, data: updateData } = await api.post(
`/admin/orders/test-order/claims/${cid}`,
{
claim_items: claim.claim_items.map((i) => ({
id: i.id,
note: "Something new",
images: [
...i.images.map((i) => ({ id: i.id })),
{ url: "https://new.com/image" },
],
tags: [
{ value: "completely" },
{ value: "NEW" },
{ value: " tags" },
],
})),
},
{
headers: {
authorization: "bearer test_token",
},
}
);
expect(status).toEqual(200);
expect(updateData.order.claims.length).toEqual(1);
claim = updateData.order.claims[0];
expect(claim.claim_items.length).toEqual(1);
expect(claim.claim_items).toEqual([
expect.objectContaining({
id: claim.claim_items[0].id,
reason: "production_failure",
note: "Something new",
images: expect.arrayContaining([
expect.objectContaining({
url: "https://test.image.com",
}),
expect.objectContaining({
url: "https://new.com/image",
}),
]),
tags: expect.arrayContaining([
expect.objectContaining({ value: "completely" }),
expect.objectContaining({ value: "new" }),
expect.objectContaining({ value: "tags" }),
]),
}),
]);
});
it("updates claim items - removes image", async () => {
const api = useApi();
const response = await api.post(
"/admin/orders/test-order/claims",
{
type: "replace",
claim_items: [
{
item_id: "test-item",
quantity: 1,
reason: "production_failure",
tags: ["fluff"],
images: ["https://test.image.com"],
},
],
additional_items: [
{
variant_id: "test-variant",
quantity: 1,
},
],
},
{
headers: {
authorization: "Bearer test_token",
},
}
);
expect(response.status).toEqual(200);
let claim = response.data.order.claims[0];
const cid = claim.id;
const { status, data: updateData } = await api.post(
`/admin/orders/test-order/claims/${cid}`,
{
claim_items: claim.claim_items.map((i) => ({
id: i.id,
note: "Something new",
images: [],
tags: [
{ value: "completely" },
{ value: "NEW" },
{ value: " tags" },
],
})),
},
{
headers: {
authorization: "bearer test_token",
},
}
);
expect(status).toEqual(200);
expect(updateData.order.claims.length).toEqual(1);
claim = updateData.order.claims[0];
expect(claim.claim_items.length).toEqual(1);
expect(claim.claim_items).toEqual([
expect.objectContaining({
id: claim.claim_items[0].id,
reason: "production_failure",
note: "Something new",
images: [],
tags: expect.arrayContaining([
expect.objectContaining({ value: "completely" }),
expect.objectContaining({ value: "new" }),
expect.objectContaining({ value: "tags" }),
]),
}),
]);
});
it("fulfills a claim", async () => {
const api = useApi();
const response = await api
.post(
"/admin/orders/test-order/claims",
{
type: "replace",
shipping_methods: [
{
id: "test-method",
},
],
claim_items: [
{
item_id: "test-item",
quantity: 1,
reason: "production_failure",
tags: ["fluff"],
images: ["https://test.image.com"],
},
],
additional_items: [
{
variant_id: "test-variant",
quantity: 1,
},
],
},
{
headers: {
Authorization: "Bearer test_token",
},
}
)
.catch((err) => {
console.log(err);
});
const cid = response.data.order.claims[0].id;
const fulRes = await api.post(
`/admin/orders/test-order/claims/${cid}/fulfillments`,
{},
{
headers: {
Authorization: "Bearer test_token",
},
}
);
expect(fulRes.status).toEqual(200);
expect(fulRes.data.order.claims).toEqual([
expect.objectContaining({
id: cid,
order_id: "test-order",
fulfillment_status: "fulfilled",
}),
]);
const fid = fulRes.data.order.claims[0].fulfillments[0].id;
const iid = fulRes.data.order.claims[0].additional_items[0].id;
expect(fulRes.data.order.claims[0].fulfillments).toEqual([
expect.objectContaining({
items: [
{
fulfillment_id: fid,
item_id: iid,
quantity: 1,
},
],
}),
]);
});
});
});

View File

@@ -0,0 +1,74 @@
const { dropDatabase } = require("pg-god");
const path = require("path");
const { Region } = require("@medusajs/medusa");
const setupServer = require("../../../helpers/setup-server");
const { useApi } = require("../../../helpers/use-api");
const { initDb } = require("../../../helpers/use-db");
jest.setTimeout(30000);
describe("/store/carts", () => {
let medusaProcess;
let dbConnection;
beforeAll(async () => {
const cwd = path.resolve(path.join(__dirname, "..", ".."));
dbConnection = await initDb({ cwd });
medusaProcess = await setupServer({ cwd });
});
afterAll(async () => {
dbConnection.close();
dropDatabase({ databaseName: "medusa-integration" });
medusaProcess.kill();
});
describe("POST /store/carts", () => {
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'`
);
});
afterEach(async () => {
const manager = dbConnection.manager;
await manager.query(`DELETE FROM "cart"`);
await manager.query(
`UPDATE "country" SET region_id=NULL WHERE iso_2 = 'us'`
);
await manager.query(`DELETE FROM "region"`);
});
it("creates 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("creates 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);
});
});
});

View File

@@ -0,0 +1,17 @@
const Scrypt = require("scrypt-kdf");
const { User } = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => {
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",
password_hash,
...data,
});
};

View File

@@ -0,0 +1,147 @@
const {
ShippingProfile,
Customer,
MoneyAmount,
LineItem,
Country,
ShippingOption,
ShippingMethod,
Product,
ProductVariant,
Region,
Order,
} = require("@medusajs/medusa");
module.exports = async (connection, data = {}) => {
const manager = connection.manager;
const defaultProfile = await manager.findOne(ShippingProfile, {
type: "default",
});
await manager.insert(Product, {
id: "test-product",
title: "test product",
profile_id: defaultProfile.id,
options: [{ id: "test-option", title: "Size" }],
});
await manager.insert(ProductVariant, {
id: "test-variant",
title: "test variant",
product_id: "test-product",
inventory_quantity: 1,
options: [
{
option_id: "test-option",
value: "Size",
},
],
});
const ma = manager.create(MoneyAmount, {
variant_id: "test-variant",
currency_code: "usd",
amount: 8000,
});
await manager.save(ma);
await manager.insert(Region, {
id: "test-region",
name: "Test Region",
currency_code: "usd",
tax_rate: 0,
});
await manager.query(
`UPDATE "country" SET region_id='test-region' WHERE iso_2 = 'us'`
);
await manager.insert(Customer, {
id: "test-customer",
email: "test@email.com",
});
await manager.insert(ShippingOption, {
id: "test-option",
name: "test-option",
provider_id: "test-ful",
region_id: "test-region",
profile_id: defaultProfile.id,
price_type: "flat_rate",
amount: 1000,
data: {},
});
const order = manager.create(Order, {
id: "test-order",
customer_id: "test-customer",
email: "test@email.com",
billing_address: {
id: "test-billing-address",
first_name: "lebron",
},
shipping_address: {
id: "test-shipping-address",
first_name: "lebron",
country_code: "us",
},
region_id: "test-region",
currency_code: "usd",
tax_rate: 0,
discounts: [
{
id: "test-discount",
code: "TEST134",
is_dynamic: false,
rule: {
id: "test-rule",
description: "Test Discount",
type: "percentage",
value: 10,
allocation: "total",
},
is_disabled: false,
regions: [
{
id: "test-region",
},
],
},
],
payments: [
{
id: "test-payment",
amount: 10000,
currency_code: "usd",
amount_refunded: 0,
provider_id: "test",
data: {},
},
],
items: [
{
id: "test-item",
fulfilled_quantity: 1,
title: "Line Item",
description: "Line Item Desc",
thumbnail: "https://test.js/1234",
unit_price: 8000,
quantity: 1,
variant_id: "test-variant",
},
],
...data,
});
await manager.save(order);
await manager.insert(ShippingMethod, {
id: "test-method",
order_id: "test-order",
shipping_option_id: "test-option",
order_id: "test-order",
price: 1000,
data: {},
});
};

View File

@@ -0,0 +1,3 @@
module.exports = {
setupFilesAfterEnv: ["<rootDir>/setup.js"],
};

View File

@@ -0,0 +1,8 @@
module.exports = {
plugins: [],
projectConfig: {
// redis_url: REDIS_URL,
database_url: "postgres://localhost/medusa-integration",
database_type: "postgres",
},
};

View File

@@ -0,0 +1,19 @@
{
"name": "api",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"build": "babel src -d dist --extensions \".ts,.js\""
},
"dependencies": {
"@medusajs/medusa": "^1.1.3",
"medusa-interfaces": "^1.1.0"
},
"devDependencies": {
"@babel/cli": "^7.12.10",
"@babel/core": "^7.12.10",
"@babel/node": "^7.12.10",
"babel-preset-medusa-package": "^1.1.0"
}
}

View File

@@ -0,0 +1,49 @@
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({});
}
createFulfillment() {
// No data is being sent anywhere
return Promise.resolve({});
}
cancelFulfillment() {
return Promise.resolve({});
}
}
export default TestFulService;

View File

@@ -0,0 +1,59 @@
import { PaymentService } from "medusa-interfaces";
class TestPayService extends PaymentService {
static identifier = "test-pay";
constructor() {
super();
}
async getStatus(paymentData) {
return "authorized";
}
async retrieveSavedMethods(customer) {
return Promise.resolve([]);
}
async createPayment() {
return {};
}
async retrievePayment(data) {
return {};
}
async getPaymentData(sessionData) {
return {};
}
async authorizePayment(sessionData, context = {}) {
return {};
}
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;

File diff suppressed because it is too large Load Diff