From 59bb413245d8aa31cc3070a372d6f0d04ebc9415 Mon Sep 17 00:00:00 2001 From: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Date: Thu, 3 Feb 2022 20:03:25 +0100 Subject: [PATCH] fix: Updating store currencies (#984) * fix: Adds default currency to store currencies on create * fix: Adds integration tests + currency update error handling * add test * Lowercase currency codes * fix tests * fix: Await drop database to stop Jest from complaining * revert itnegration tests fix * fix store tests --- .../admin/__snapshots__/store.js.snap | 144 ++++++++++++ .../api/__tests__/admin/store.js | 221 ++++++++++++++++++ integration-tests/setup.js | 4 +- .../medusa/src/services/__tests__/store.js | 9 +- packages/medusa/src/services/store.js | 50 +++- 5 files changed, 412 insertions(+), 16 deletions(-) create mode 100644 integration-tests/api/__tests__/admin/__snapshots__/store.js.snap create mode 100644 integration-tests/api/__tests__/admin/store.js diff --git a/integration-tests/api/__tests__/admin/__snapshots__/store.js.snap b/integration-tests/api/__tests__/admin/__snapshots__/store.js.snap new file mode 100644 index 0000000000..d8fe3ab0e3 --- /dev/null +++ b/integration-tests/api/__tests__/admin/__snapshots__/store.js.snap @@ -0,0 +1,144 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`/admin/store POST /admin/store fails to update default currency if not in store currencies 1`] = ` +Object { + "message": "Store does not have currency: eur", + "type": "invalid_data", +} +`; + +exports[`/admin/store POST /admin/store successfully updates and store currencies 1`] = ` +Object { + "created_at": Any, + "currencies": Array [ + Object { + "code": "jpy", + "name": "Japanese Yen", + "symbol": "¥", + "symbol_native": "¥", + }, + Object { + "code": "usd", + "name": "US Dollar", + "symbol": "$", + "symbol_native": "$", + }, + ], + "default_currency_code": "usd", + "id": Any, + "invite_link_template": null, + "metadata": null, + "name": "Medusa Store", + "payment_link_template": null, + "swap_link_template": null, + "updated_at": Any, +} +`; + +exports[`/admin/store POST /admin/store successfully updates default currency and store currencies 1`] = ` +Object { + "created_at": Any, + "currencies": Array [ + Object { + "code": "jpy", + "name": "Japanese Yen", + "symbol": "¥", + "symbol_native": "¥", + }, + Object { + "code": "usd", + "name": "US Dollar", + "symbol": "$", + "symbol_native": "$", + }, + ], + "default_currency": Object { + "code": "jpy", + "name": "Japanese Yen", + "symbol": "¥", + "symbol_native": "¥", + }, + "default_currency_code": "jpy", + "id": Any, + "invite_link_template": null, + "metadata": null, + "name": "Medusa Store", + "payment_link_template": null, + "swap_link_template": null, + "updated_at": Any, +} +`; + +exports[`/admin/store POST /admin/store successfully updates default currency code 1`] = ` +Object { + "created_at": Any, + "currencies": Array [ + Object { + "code": "usd", + "name": "US Dollar", + "symbol": "$", + "symbol_native": "$", + }, + Object { + "code": "dkk", + "name": "Danish Krone", + "symbol": "Dkr", + "symbol_native": "kr", + }, + ], + "default_currency": Object { + "code": "dkk", + "name": "Danish Krone", + "symbol": "Dkr", + "symbol_native": "kr", + }, + "default_currency_code": "dkk", + "id": Any, + "invite_link_template": null, + "metadata": null, + "name": "Medusa Store", + "payment_link_template": null, + "swap_link_template": null, + "updated_at": Any, +} +`; + +exports[`/admin/store Store creation has created store with default currency 1`] = ` +Object { + "created_at": Any, + "currencies": Array [ + Object { + "code": "usd", + "name": "US Dollar", + "symbol": "$", + "symbol_native": "$", + }, + ], + "default_currency": Object { + "code": "usd", + "name": "US Dollar", + "symbol": "$", + "symbol_native": "$", + }, + "default_currency_code": "usd", + "fulfillment_providers": Array [ + Object { + "id": "test-ful", + "is_installed": true, + }, + ], + "id": Any, + "invite_link_template": null, + "metadata": null, + "name": "Medusa Store", + "payment_link_template": null, + "payment_providers": Array [ + Object { + "id": "test-pay", + "is_installed": true, + }, + ], + "swap_link_template": null, + "updated_at": Any, +} +`; diff --git a/integration-tests/api/__tests__/admin/store.js b/integration-tests/api/__tests__/admin/store.js new file mode 100644 index 0000000000..6b0fbea9f9 --- /dev/null +++ b/integration-tests/api/__tests__/admin/store.js @@ -0,0 +1,221 @@ +const { Store } = require("@medusajs/medusa") +const path = require("path") + +const setupServer = require("../../../helpers/setup-server") +const { useApi } = require("../../../helpers/use-api") +const { initDb, useDb } = require("../../../helpers/use-db") + +const adminSeeder = require("../../helpers/admin-seeder") + +jest.setTimeout(30000) + +describe("/admin/store", () => { + let dbConnection + const cwd = path.resolve(path.join(__dirname, "..", "..")) + + beforeAll(async () => { + dbConnection = await initDb({ cwd }) + }) + + afterAll(async () => { + const db = useDb() + await db.shutdown() + }) + + describe("Store creation", () => { + let medusaProcess + + beforeEach(async () => { + await adminSeeder(dbConnection) + medusaProcess = await setupServer({ cwd }) + }) + + afterEach(async () => { + const db = useDb() + db.teardown() + medusaProcess.kill() + }) + + it("has created store with default currency", async () => { + const api = useApi() + + const response = await api.get("/admin/store", { + headers: { Authorization: "Bearer test_token " }, + }) + + expect(response.status).toEqual(200) + expect(response.data.store).toMatchSnapshot({ + id: expect.any(String), + name: "Medusa Store", + currencies: [ + { + code: "usd", + }, + ], + default_currency_code: "usd", + created_at: expect.any(String), + updated_at: expect.any(String), + }) + }) + }) + + describe("POST /admin/store", () => { + let medusaProcess + + beforeEach(async () => { + await adminSeeder(dbConnection) + medusaProcess = await setupServer({ cwd }) + + const manager = dbConnection.manager + const store = await manager.findOne(Store, { name: "Medusa Store" }) + await manager.query( + `INSERT INTO store_currencies (store_id, currency_code) VALUES ('${store.id}', 'dkk')` + ) + }) + + afterEach(async () => { + const db = useDb() + await db.teardown() + medusaProcess.kill() + }) + + it("fails to update default currency if not in store currencies", async () => { + const api = useApi() + + try { + await api.post( + "/admin/store", + { + default_currency_code: "eur", + }, + { + headers: { Authorization: "Bearer test_token " }, + } + ) + } catch (e) { + expect(e.response.data).toMatchSnapshot({ + type: "invalid_data", + message: "Store does not have currency: eur", + }) + expect(e.response.status).toBe(400) + } + }) + + it("fails to remove default currency from currencies without replacing it", async () => { + const api = useApi() + + try { + await api.post( + "/admin/store", + { + currencies: ["usd"], + }, + { + headers: { Authorization: "Bearer test_token " }, + } + ) + } catch (e) { + expect(e.response.data).toMatchSnapshot({ + type: "invalid_data", + message: + "You are not allowed to remove default currency from store currencies without replacing it as well", + }) + expect(e.response.status).toBe(400) + } + }) + + it("successfully updates default currency code", async () => { + const api = useApi() + + const response = await api.post( + "/admin/store", + { + default_currency_code: "dkk", + }, + { + headers: { Authorization: "Bearer test_token " }, + } + ) + + expect(response.status).toEqual(200) + expect(response.data.store).toMatchSnapshot({ + id: expect.any(String), + name: "Medusa Store", + currencies: [ + { + code: "usd", + }, + { + code: "dkk", + }, + ], + default_currency_code: "dkk", + created_at: expect.any(String), + updated_at: expect.any(String), + }) + }) + + it("successfully updates default currency and store currencies", async () => { + const api = useApi() + + const response = await api.post( + "/admin/store", + { + default_currency_code: "jpy", + currencies: ["jpy", "usd"], + }, + { + headers: { Authorization: "Bearer test_token " }, + } + ) + + expect(response.status).toEqual(200) + expect(response.data.store).toMatchSnapshot({ + id: expect.any(String), + name: "Medusa Store", + currencies: [ + { + code: "jpy", + }, + { + code: "usd", + }, + ], + default_currency_code: "jpy", + created_at: expect.any(String), + updated_at: expect.any(String), + }) + }) + + it("successfully updates and store currencies", async () => { + const api = useApi() + + const response = await api.post( + "/admin/store", + { + currencies: ["jpy", "usd"], + }, + { + headers: { Authorization: "Bearer test_token " }, + } + ) + + expect(response.status).toEqual(200) + expect(response.data.store).toMatchSnapshot({ + id: expect.any(String), + name: "Medusa Store", + currencies: [ + { + code: "jpy", + }, + { + code: "usd", + }, + ], + default_currency_code: "usd", + created_at: expect.any(String), + updated_at: expect.any(String), + }) + }) + }) +}) diff --git a/integration-tests/setup.js b/integration-tests/setup.js index 422d6f58c9..54a3d0eb44 100644 --- a/integration-tests/setup.js +++ b/integration-tests/setup.js @@ -11,9 +11,9 @@ const pgGodCredentials = { password: DB_PASSWORD, } -afterAll(() => { +afterAll(async () => { const workerId = parseInt(process.env.JEST_WORKER_ID || "1") - dropDatabase( + await dropDatabase( { databaseName: `medusa-integration-${workerId}` }, pgGodCredentials ) diff --git a/packages/medusa/src/services/__tests__/store.js b/packages/medusa/src/services/__tests__/store.js index e2630da040..1045fc14c0 100644 --- a/packages/medusa/src/services/__tests__/store.js +++ b/packages/medusa/src/services/__tests__/store.js @@ -53,7 +53,11 @@ describe("StoreService", () => { describe("update", () => { const storeRepository = MockRepository({ findOne: () => - Promise.resolve({ id: IdMap.getId("store"), name: "Medusa" }), + Promise.resolve({ + id: IdMap.getId("store"), + name: "Medusa", + default_currency_code: "usd", + }), }) const currencyRepository = MockRepository({}) @@ -79,13 +83,14 @@ describe("StoreService", () => { expect(storeRepository.save).toHaveBeenCalledWith({ id: IdMap.getId("store"), name: "Medusa Commerce", + default_currency_code: "usd", }) }) it("fails if currency not ok", async () => { await expect( storeService.update({ - currencies: ["1cd"], + currencies: ["1cd", "usd"], }) ).rejects.toThrow("Invalid currency 1cd") diff --git a/packages/medusa/src/services/store.js b/packages/medusa/src/services/store.js index b3bcea71f3..0f10024092 100644 --- a/packages/medusa/src/services/store.js +++ b/packages/medusa/src/services/store.js @@ -112,7 +112,7 @@ class StoreService extends BaseService { this.currencyRepository_ ) - const store = await this.retrieve() + const store = await this.retrieve(["currencies"]) const { metadata, @@ -125,23 +125,20 @@ class StoreService extends BaseService { store.metadata = this.setMetadata_(store.id, metadata) } - if (default_currency_code) { - const curr = await currencyRepository.findOne({ - code: default_currency_code.toLowerCase(), - }) + if (storeCurrencies) { + const defaultCurr = default_currency_code ?? store.default_currency_code + const hasDefCurrency = storeCurrencies.find( + (c) => c.toLowerCase() === defaultCurr.toLowerCase() + ) - if (!curr) { + // throw if we are trying to remove a currency from store currently used as default + if (!hasDefCurrency) { throw new MedusaError( MedusaError.Types.INVALID_DATA, - `Currency ${default_currency_code} not found` + `You are not allowed to remove default currency from store currencies without replacing it as well` ) } - store.default_currency = curr - store.default_currency_code = curr.code - } - - if (storeCurrencies) { store.currencies = await Promise.all( storeCurrencies.map(async (curr) => { const currency = await currencyRepository.findOne({ @@ -160,6 +157,35 @@ class StoreService extends BaseService { ) } + if (default_currency_code) { + const storeCurrCodes = store.currencies.map((c) => c.code) + const hasDefCurrency = storeCurrCodes.find( + (c) => c === default_currency_code.toLowerCase() + ) + + // throw if store currencies does not have default currency + if (!hasDefCurrency) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Store does not have currency: ${default_currency_code}` + ) + } + + const curr = await currencyRepository.findOne({ + code: default_currency_code.toLowerCase(), + }) + + if (!curr) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Currency ${default_currency_code} not found` + ) + } + + store.default_currency = curr + store.default_currency_code = curr.code + } + for (const [key, value] of Object.entries(rest)) { store[key] = value }