diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 90e9730..fe216d0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -41,7 +41,6 @@ jobs: run: | npm install npm run prisma - npm run seed - name: Run Tests uses: ArtiomTr/jest-coverage-report-action@v2 id: coverage diff --git a/.local.docker.env b/.local.docker.env index 2cf3c1e..afcce2a 100644 --- a/.local.docker.env +++ b/.local.docker.env @@ -2,3 +2,9 @@ DB_HOST=db DB_PORT=3306 DATABASE_URL=mysql://root:root@db/db SENTRY_DSN=__INSERT_YOUR_SENTRY_DSN_HERE__ +# if you want to use send SMS feature, you need to set these environment variables +# NCP_SENS_SECRET=__INSERT_YOUR_NCP_SENS_SECRET_HERE__ +# NCP_SENS_ACCESS=__INSERT_YOUR_NCP_SENS_ACCESS_HERE__ +# NCP_SENS_MY_NUMBER=__INSERT_YOUR_NCP_SENS_MY_NUMBER_HERE__ +# NCP_SENS_ID=__INSERT_YOUR_NCP_SENS_ID_HERE__ +# NODE_ENV=production diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 06af164..a772cba 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -8,6 +8,6 @@ if [ "$NODE_ENV" = "production" ]; then npm run prisma:deploy else npm run prisma - npm run seed + fi npm run dev diff --git a/jest.config.js b/jest.config.js index 2228686..79942ca 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,6 @@ /** @type {import('ts-jest').JestConfigWithTsJest} */ export default { - preset: 'ts-jest', + preset: 'ts-jest/presets/default-esm', testEnvironment: 'node', collectCoverageFrom: [ 'src/**/*.[jt]s?(x)', @@ -8,6 +8,7 @@ export default { '!src/index.ts', '!src/loaders/index.ts', '!src/config/index.ts', + '!src/utils/sendSMS.ts', ], moduleNameMapper: { '^@api$': '/src/api', @@ -22,5 +23,6 @@ export default { '^@routes/(.*)$': '/src/api/routes/$1', '^@hooks/(.*)$': '/src/api/hooks/$1', '^@server$': '/src/server.ts', + '^@seeding/(.*)$': '/seeding/$1', }, }; diff --git a/package.json b/package.json index 6441f69..1979aa6 100644 --- a/package.json +++ b/package.json @@ -5,15 +5,11 @@ "type": "module", "scripts": { "dev": "tsx watch src src/index.ts", - "test": "prisma migrate reset -f && jest --detectOpenHandles --forceExit", - "test-coverage": "prisma migrate reset -f && jest --coverage --detectOpenHandles --forceExit", + "test": "prisma migrate reset -f && node --experimental-vm-modules node_modules/jest/bin/jest.js", + "test-coverage": "prisma migrate reset -f && node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage", "prisma": "./prisma/concat_schema.sh && prisma migrate dev --name init --preview-feature", "prisma:deploy": "./prisma/concat_schema.sh && prisma migrate deploy --preview-feature", - "prisma:generate": "./prisma/concat_schema.sh && prisma generate", - "seed": "prisma db seed" - }, - "prisma": { - "seed": "tsx prisma/seed.ts" + "prisma:generate": "./prisma/concat_schema.sh && prisma generate" }, "keywords": [], "author": "", diff --git a/prisma/migrations/20231011061113_init/migration.sql b/prisma/migrations/20231011061113_init/migration.sql deleted file mode 100644 index c5d766f..0000000 --- a/prisma/migrations/20231011061113_init/migration.sql +++ /dev/null @@ -1,37 +0,0 @@ --- CreateTable -CREATE TABLE `Recipe` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `storeId` INTEGER NOT NULL, - `menuId` INTEGER NOT NULL, - `stockId` INTEGER NOT NULL, - `coldRegularAmount` INTEGER NULL, - `coldSizeUpAmount` INTEGER NULL, - `hotRegularAmount` INTEGER NULL, - `hotSizeUpAmount` INTEGER NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Stock` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `storeId` INTEGER NOT NULL, - `name` VARCHAR(191) NOT NULL, - `standard` INTEGER NOT NULL, - `count` INTEGER NOT NULL, - `sort` INTEGER NOT NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- AddForeignKey -ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_menuId_fkey` FOREIGN KEY (`menuId`) REFERENCES `Menu`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_stockId_fkey` FOREIGN KEY (`stockId`) REFERENCES `Stock`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Stock` ADD CONSTRAINT `Stock_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20231012124817_init/migration.sql b/prisma/migrations/20231012124817_init/migration.sql deleted file mode 100644 index 9dfa4f3..0000000 --- a/prisma/migrations/20231012124817_init/migration.sql +++ /dev/null @@ -1,61 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `count` on the `Stock` table. All the data in the column will be lost. - - You are about to drop the column `sort` on the `Stock` table. All the data in the column will be lost. - - You are about to drop the column `standard` on the `Stock` table. All the data in the column will be lost. - -*/ --- DropForeignKey -ALTER TABLE `Recipe` DROP FOREIGN KEY `Recipe_stockId_fkey`; - --- AlterTable -ALTER TABLE `Recipe` ADD COLUMN `mixedStockId` INTEGER NULL, - MODIFY `stockId` INTEGER NULL; - --- AlterTable -ALTER TABLE `Stock` DROP COLUMN `count`, - DROP COLUMN `sort`, - DROP COLUMN `standard`, - ADD COLUMN `amount` INTEGER NULL, - ADD COLUMN `unit` VARCHAR(191) NULL; - --- CreateTable -CREATE TABLE `MixedStock` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `storeId` INTEGER NOT NULL, - `name` VARCHAR(191) NOT NULL, - `totalAmount` INTEGER NULL, - `unit` VARCHAR(191) NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- CreateTable -CREATE TABLE `Mixing` ( - `id` INTEGER NOT NULL AUTO_INCREMENT, - `storeId` INTEGER NOT NULL, - `mixedStockId` INTEGER NOT NULL, - `stockId` INTEGER NOT NULL, - `amount` INTEGER NOT NULL, - - PRIMARY KEY (`id`) -) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; - --- AddForeignKey -ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_stockId_fkey` FOREIGN KEY (`stockId`) REFERENCES `Stock`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_mixedStockId_fkey` FOREIGN KEY (`mixedStockId`) REFERENCES `MixedStock`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `MixedStock` ADD CONSTRAINT `MixedStock_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Mixing` ADD CONSTRAINT `Mixing_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Mixing` ADD CONSTRAINT `Mixing_mixedStockId_fkey` FOREIGN KEY (`mixedStockId`) REFERENCES `MixedStock`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE `Mixing` ADD CONSTRAINT `Mixing_stockId_fkey` FOREIGN KEY (`stockId`) REFERENCES `Stock`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20231013122529_init/migration.sql b/prisma/migrations/20231013122529_init/migration.sql deleted file mode 100644 index fae0414..0000000 --- a/prisma/migrations/20231013122529_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `Stock` ADD COLUMN `price` DECIMAL(65, 30) NULL; diff --git a/prisma/migrations/20231018133609_init/migration.sql b/prisma/migrations/20231018133609_init/migration.sql deleted file mode 100644 index 5ba503d..0000000 --- a/prisma/migrations/20231018133609_init/migration.sql +++ /dev/null @@ -1,21 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `storeId` on the `Mixing` table. All the data in the column will be lost. - - You are about to drop the column `storeId` on the `Recipe` table. All the data in the column will be lost. - -*/ --- DropForeignKey -ALTER TABLE `Mixing` DROP FOREIGN KEY `Mixing_storeId_fkey`; - --- DropForeignKey -ALTER TABLE `Recipe` DROP FOREIGN KEY `Recipe_storeId_fkey`; - --- AlterTable -ALTER TABLE `Mixing` DROP COLUMN `storeId`; - --- AlterTable -ALTER TABLE `Recipe` DROP COLUMN `storeId`; - --- AlterTable -ALTER TABLE `Stock` ADD COLUMN `currentAmount` INTEGER NOT NULL DEFAULT 0; diff --git a/prisma/migrations/20231021100541_init/migration.sql b/prisma/migrations/20231021100541_init/migration.sql deleted file mode 100644 index 7ba36a1..0000000 --- a/prisma/migrations/20231021100541_init/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* - Warnings: - - - Added the required column `updatedAt` to the `Stock` table without a default value. This is not possible if the table is not empty. - -*/ --- AlterTable -ALTER TABLE `Stock` ADD COLUMN `noticeThreshold` INTEGER NOT NULL DEFAULT 0, - ADD COLUMN `updatedAt` DATETIME(3) NOT NULL, - MODIFY `currentAmount` INTEGER NULL; diff --git a/prisma/migrations/20231023140257_init/migration.sql b/prisma/migrations/20231023140257_init/migration.sql deleted file mode 100644 index 762f2ef..0000000 --- a/prisma/migrations/20231023140257_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `Category` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231023141607_init/migration.sql b/prisma/migrations/20231023141607_init/migration.sql deleted file mode 100644 index 04c7914..0000000 --- a/prisma/migrations/20231023141607_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `Menu` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231023141928_init/migration.sql b/prisma/migrations/20231023141928_init/migration.sql deleted file mode 100644 index 8fb0609..0000000 --- a/prisma/migrations/20231023141928_init/migration.sql +++ /dev/null @@ -1,5 +0,0 @@ --- AlterTable -ALTER TABLE `MixedStock` ADD COLUMN `deletedAt` DATETIME(3) NULL; - --- AlterTable -ALTER TABLE `Stock` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231023142813_init/migration.sql b/prisma/migrations/20231023142813_init/migration.sql deleted file mode 100644 index b7fe25a..0000000 --- a/prisma/migrations/20231023142813_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `Payment` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231023143459_init/migration.sql b/prisma/migrations/20231023143459_init/migration.sql deleted file mode 100644 index a35d2a3..0000000 --- a/prisma/migrations/20231023143459_init/migration.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - Warnings: - - - You are about to drop the column `deletedAt` on the `Payment` table. All the data in the column will be lost. - -*/ --- AlterTable -ALTER TABLE `Order` ADD COLUMN `deletedAt` DATETIME(3) NULL; - --- AlterTable -ALTER TABLE `Payment` DROP COLUMN `deletedAt`; diff --git a/prisma/migrations/20231023143545_init/migration.sql b/prisma/migrations/20231023143545_init/migration.sql deleted file mode 100644 index a874aa7..0000000 --- a/prisma/migrations/20231023143545_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `PreOrder` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231023144220_init/migration.sql b/prisma/migrations/20231023144220_init/migration.sql deleted file mode 100644 index b49509f..0000000 --- a/prisma/migrations/20231023144220_init/migration.sql +++ /dev/null @@ -1,2 +0,0 @@ --- AlterTable -ALTER TABLE `Option` ADD COLUMN `deletedAt` DATETIME(3) NULL; diff --git a/prisma/migrations/20231025074215_init/migration.sql b/prisma/migrations/20231025074215_init/migration.sql new file mode 100644 index 0000000..ae5c82c --- /dev/null +++ b/prisma/migrations/20231025074215_init/migration.sql @@ -0,0 +1,87 @@ +-- AlterTable +ALTER TABLE `Category` ADD COLUMN `deletedAt` DATETIME(3) NULL; + +-- AlterTable +ALTER TABLE `Menu` ADD COLUMN `deletedAt` DATETIME(3) NULL; + +-- AlterTable +ALTER TABLE `Option` ADD COLUMN `deletedAt` DATETIME(3) NULL; + +-- AlterTable +ALTER TABLE `Order` ADD COLUMN `deletedAt` DATETIME(3) NULL; + +-- AlterTable +ALTER TABLE `PreOrder` ADD COLUMN `deletedAt` DATETIME(3) NULL; + +-- CreateTable +CREATE TABLE `Recipe` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `menuId` INTEGER NOT NULL, + `stockId` INTEGER NULL, + `mixedStockId` INTEGER NULL, + `coldRegularAmount` INTEGER NULL, + `coldSizeUpAmount` INTEGER NULL, + `hotRegularAmount` INTEGER NULL, + `hotSizeUpAmount` INTEGER NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `Stock` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `updatedAt` DATETIME(3) NOT NULL, + `storeId` INTEGER NOT NULL, + `name` VARCHAR(191) NOT NULL, + `amount` INTEGER NULL, + `unit` VARCHAR(191) NULL, + `price` DECIMAL(65, 30) NULL, + `currentAmount` INTEGER NULL, + `noticeThreshold` INTEGER NOT NULL DEFAULT 0, + `deletedAt` DATETIME(3) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `MixedStock` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `storeId` INTEGER NOT NULL, + `name` VARCHAR(191) NOT NULL, + `totalAmount` INTEGER NULL, + `unit` VARCHAR(191) NULL, + `deletedAt` DATETIME(3) NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- CreateTable +CREATE TABLE `Mixing` ( + `id` INTEGER NOT NULL AUTO_INCREMENT, + `mixedStockId` INTEGER NOT NULL, + `stockId` INTEGER NOT NULL, + `amount` INTEGER NOT NULL, + + PRIMARY KEY (`id`) +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +-- AddForeignKey +ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_menuId_fkey` FOREIGN KEY (`menuId`) REFERENCES `Menu`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_stockId_fkey` FOREIGN KEY (`stockId`) REFERENCES `Stock`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Recipe` ADD CONSTRAINT `Recipe_mixedStockId_fkey` FOREIGN KEY (`mixedStockId`) REFERENCES `MixedStock`(`id`) ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Stock` ADD CONSTRAINT `Stock_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `MixedStock` ADD CONSTRAINT `MixedStock_storeId_fkey` FOREIGN KEY (`storeId`) REFERENCES `Store`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Mixing` ADD CONSTRAINT `Mixing_mixedStockId_fkey` FOREIGN KEY (`mixedStockId`) REFERENCES `MixedStock`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE `Mixing` ADD CONSTRAINT `Mixing_stockId_fkey` FOREIGN KEY (`stockId`) REFERENCES `Stock`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/seed.ts b/prisma/seed.ts deleted file mode 100644 index d164d9d..0000000 --- a/prisma/seed.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { PrismaClient } from '@prisma/client'; -import menuSeed from './menuSeed'; -const prisma = new PrismaClient(); -async function main() { - const user = await prisma.user.create({ - data: { - id: 1, - businessRegistrationNumber: '5133001104', - phoneNumber: '01012345678', - }, - }); //user id 1 - - const store = await prisma.store.create({ - data: { - id: 1, - userId: 1, - name: '소예다방', - address: '고려대 근처', - defaultOpeningHours: [ - { - yoil: '일', - start: null, - end: null, //휴무일은 시작시간과 종료시간 모두 null 이어야함 - }, - { - yoil: '월', - start: '09:00', - end: '18:00', - }, - ], - createdAt: new Date(), - updatedAt: new Date(), - }, - }); //store id 1 - - const store2 = await prisma.store.create({ - data: { - id: 2, - userId: 1, - name: '소예다방', - address: '고려대 근처', - defaultOpeningHours: [ - { - yoil: '일', - start: null, - end: null, //휴무일은 시작시간과 종료시간 모두 null 이어야함 - }, - { - yoil: '월', - start: '09:00', - end: '18:00', - }, - ], - createdAt: new Date(), - updatedAt: new Date(), - }, - }); //store id 2 - - const category1 = await prisma.category.create({ - data: { - id: 1, - storeId: 1, - name: '커피', - sort: 1, - createdAt: new Date(), - updatedAt: new Date(), - }, - }); - - const category2 = await prisma.category.create({ - data: { - id: 2, - storeId: 1, - name: '티&에이드', - sort: 2, - createdAt: new Date(), - updatedAt: new Date(), - }, - }); - - const option = await Promise.all( - [ - { - optionName: 'ice', - optionPrice: 0, - optionCategory: '온도', - storeId: 1, - }, - { - optionName: 'hot', - optionPrice: 0, - optionCategory: '온도', - storeId: 1, - }, - { - optionName: '케냐', - optionPrice: 0, - optionCategory: '원두', - storeId: 1, - }, - { - optionName: '콜롬비아', - optionPrice: 300, - optionCategory: '원두', - storeId: 1, - }, - { - optionName: '1샷 추가', - optionPrice: 500, - optionCategory: '샷', - storeId: 1, - }, - { - optionName: '연하게', - optionPrice: 0, - optionCategory: '샷', - storeId: 1, - }, - ].map(async (option, index) => { - return await prisma.option.create({ - data: { - id: index + 1, - ...option, - }, - }); - }) - ); - - const menu = await Promise.all( - [ - { - name: '아메리카노', - price: 2000, - storeId: 1, - categoryId: 1, - sort: 1, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - name: '카페라떼', - price: 3000, - storeId: 1, - categoryId: 1, - sort: 2, - createdAt: new Date(), - updatedAt: new Date(), - }, - { - name: '아이스티', - price: 2500, - storeId: 1, - categoryId: 2, - sort: 1, - createdAt: new Date(), - updatedAt: new Date(), - }, - ].map(async (menu, index) => { - return await prisma.menu.create({ - data: { id: index + 1, ...menu }, - }); - }) - ); - - await Promise.all( - menu.flatMap((menu) => { - return option.map((option) => { - if (menu.name === '아이스티' && option.optionName === 'hot') return; - if (menu.name === '아이스티' && option.optionName === '연하게') return; - return prisma.optionMenu.create({ - data: { - menuId: menu.id, - optionId: option.id, - }, - }); - }); - }) - ); - - const order = await prisma.order.create({ - data: { - storeId: 1, - paymentStatus: 'WAITING', - totalPrice: 7500, - orderitems: { - create: menu.map((menu) => { - return { - count: 1, - menuId: menu.id, - }; - }), - }, - createdAt: new Date( - new Date('2022-01-01 00:00:00').getTime() + 9 * 60 * 60 * 1000 - ), - updatedAt: new Date( - new Date('2022-01-01 00:00:00').getTime() + 9 * 60 * 60 * 1000 - ), - }, - }); - const mileage = await prisma.mileage.create({ - data: { - phone: '01023456789', - mileage: 0, - storeId: store.id, - }, - }); - await menuSeed(prisma, store.id); -} - -main() - .catch((e) => console.log(e)) - .finally(async () => { - await prisma.$disconnect(); - }); diff --git "a/prisma/\354\206\214\354\230\210\353\213\244\353\260\251.json" "b/prisma/\354\206\214\354\230\210\353\213\244\353\260\251.json" deleted file mode 100644 index f60d9ce..0000000 --- "a/prisma/\354\206\214\354\230\210\353\213\244\353\260\251.json" +++ /dev/null @@ -1,15 +0,0 @@ -[ - { - "name": "아메리카노", - "ice/hot": "", - "size": "", - "price": "3000", - "재료": [ - { - "재료이름": "원두", - "재료양": "10g" - } - ] - } - -] diff --git a/seeding/defaultSeed.ts b/seeding/defaultSeed.ts new file mode 100644 index 0000000..e70c281 --- /dev/null +++ b/seeding/defaultSeed.ts @@ -0,0 +1,83 @@ +import { PrismaClient } from '@prisma/client'; + +export default async (prisma: PrismaClient, storeId: number) => { + await prisma.category.create({ + data: { + name: '커피', + sort: 1, + storeId, + }, + }); + await prisma.stock.createMany({ + data: [ + { + name: '물', + unit: 'ml', + noticeThreshold: -1, + storeId, + }, + { + name: '원두(예시)', + unit: 'g', + noticeThreshold: 1000, + currentAmount: 2000, + storeId, + }, + { + name: '우유(예시)', + unit: 'ml', + noticeThreshold: 1000, + currentAmount: 500, + storeId, + } + ] + }); + + await prisma.menu.create({ + data: + { + name: '아메리카노(예시)', + sort: 1, + price: 2500, + categoryId: 1, + storeId, + optionMenu: { + create: [1, 2, 3, 4, 5, 6].map((optionId) => ({ optionId })) + }, + recipes: { + create: [ + { + stockId: 1, + coldRegularAmount: 150, + }, { + stockId: 2, + coldRegularAmount: 25, + } + ] + } + } + }); + await prisma.menu.create({ + data: { + name: '카페라떼(예시)', + price: 3000, + sort: 2, + categoryId: 1, + storeId, + optionMenu: { + create: [1, 2, 3, 4, 5, 6].map((optionId) => ({ optionId })) + }, + recipes: { + create: [ + { + stockId: 2, + coldRegularAmount: 25, + }, { + stockId: 3, + coldRegularAmount: 150, + } + ] + } + } + }); +} diff --git a/prisma/menuSeed.ts b/seeding/testSeed.ts similarity index 91% rename from prisma/menuSeed.ts rename to seeding/testSeed.ts index 6125c22..49243e0 100644 --- a/prisma/menuSeed.ts +++ b/seeding/testSeed.ts @@ -1,8 +1,8 @@ import * as fs from 'fs'; -import * as path from 'path'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { PrismaClient } from '@prisma/client'; - -const materialRawData = fs.readFileSync(path.join(process.cwd(),'prisma', '소예다방-재료.csv'), 'utf8').toString().trim(); +const materialRawData = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '소예다방-재료.csv'), 'utf8').toString().trim(); const materialData = materialRawData.split('\n').slice(2).map((row) => { const [name, amount, unit, price] = row.split(','); return { @@ -13,7 +13,7 @@ const materialData = materialRawData.split('\n').slice(2).map((row) => { }; }); -const mixedMaterialRawData = fs.readFileSync(path.join(process.cwd(),'prisma', '소예다방-Mixed재료.csv'), 'utf8').toString().trim(); +const mixedMaterialRawData = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '소예다방-Mixed재료.csv'), 'utf8').toString().trim(); const mixedMaterialData = mixedMaterialRawData.split('\n').slice(2).join('\n').split('\n,,\n').map((material) => { const rows = material.split('\n'); const name = rows[0].split(',')[0]; @@ -27,7 +27,7 @@ const mixedMaterialData = mixedMaterialRawData.split('\n').slice(2).join('\n').s return { name, materials }; }); -const menuRawData = fs.readFileSync(path.join(process.cwd(),'prisma', '소예다방-메뉴.csv'), 'utf8').toString().trim(); +const menuRawData = fs.readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), '소예다방-메뉴.csv'), 'utf8').toString().trim(); const menuData = menuRawData.split('\n').slice(3).join('\n').split('\n,,,,,,,\n').map((menu) => { const rows = menu.split('\n'); const name = rows[0].split(',')[0]; diff --git "a/prisma/\354\206\214\354\230\210\353\213\244\353\260\251-Mixed\354\236\254\353\243\214.csv" "b/seeding/\354\206\214\354\230\210\353\213\244\353\260\251-Mixed\354\236\254\353\243\214.csv" similarity index 100% rename from "prisma/\354\206\214\354\230\210\353\213\244\353\260\251-Mixed\354\236\254\353\243\214.csv" rename to "seeding/\354\206\214\354\230\210\353\213\244\353\260\251-Mixed\354\236\254\353\243\214.csv" diff --git "a/prisma/\354\206\214\354\230\210\353\213\244\353\260\251-\353\251\224\353\211\264.csv" "b/seeding/\354\206\214\354\230\210\353\213\244\353\260\251-\353\251\224\353\211\264.csv" similarity index 100% rename from "prisma/\354\206\214\354\230\210\353\213\244\353\260\251-\353\251\224\353\211\264.csv" rename to "seeding/\354\206\214\354\230\210\353\213\244\353\260\251-\353\251\224\353\211\264.csv" diff --git "a/prisma/\354\206\214\354\230\210\353\213\244\353\260\251-\354\236\254\353\243\214.csv" "b/seeding/\354\206\214\354\230\210\353\213\244\353\260\251-\354\236\254\353\243\214.csv" similarity index 100% rename from "prisma/\354\206\214\354\230\210\353\213\244\353\260\251-\354\236\254\353\243\214.csv" rename to "seeding/\354\206\214\354\230\210\353\213\244\353\260\251-\354\236\254\353\243\214.csv" diff --git a/src/DTO/menu.dto.ts b/src/DTO/menu.dto.ts index 88aa5cf..b409db7 100644 --- a/src/DTO/menu.dto.ts +++ b/src/DTO/menu.dto.ts @@ -282,13 +282,9 @@ export const softDeleteCategorySchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['categoryId'], - properties: { - categoryId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, @@ -308,7 +304,7 @@ export const createMenuSchema = { required: ['name', 'price', 'categoryId'], properties: { name: { type: 'string' }, - price: { type: 'number' }, + price: { type: 'string' }, categoryId: { type: 'number' }, option: { type: 'array', @@ -357,7 +353,7 @@ export const createMenuSchema = { export const updateMenuSchema = { tags: ['menu'], - summary: '메뉴 생성', + summary: '메뉴 수정', headers: StoreAuthorizationHeader, body: { type: 'object', @@ -365,7 +361,7 @@ export const updateMenuSchema = { properties: { id: { type: 'number' }, name: { type: 'string' }, - price: { type: 'number' }, + price: { type: 'string' }, categoryId: { type: 'number' }, option: { type: 'array', @@ -423,13 +419,9 @@ export const softDeleteMenuSchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['menuId'], - properties: { - menuId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, @@ -452,13 +444,9 @@ export const softDeletOptionSchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['optionId'], - properties: { - optionId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, diff --git a/src/DTO/order.dto.ts b/src/DTO/order.dto.ts index db24d87..427bce8 100644 --- a/src/DTO/order.dto.ts +++ b/src/DTO/order.dto.ts @@ -76,7 +76,6 @@ export const getOrderSchema = { 'totalPrice', 'createdAt', 'orderitems', - 'pay', 'isPreOrdered', ], properties: { @@ -127,6 +126,7 @@ export const getOrderSchema = { price: { type: 'string' }, }, }, + isPreOrdered: { type: 'boolean' }, }, }, ...errorSchema( @@ -189,6 +189,7 @@ export const getOrderListSchema = { totalCount: { type: 'number' }, totalPrice: { type: 'string' }, createdAt: { type: 'string', format: 'date-time' }, + isPreOrdered: { type: 'boolean' }, }, }, }, @@ -230,7 +231,8 @@ export const paySchema = { E.StoreAuthorizationError, E.NoAuthorizationInHeaderError, E.NotCorrectTypeError, - E.NotEnoughError + E.NotEnoughError, + E.AlreadyPaidError ), }, } as const; @@ -247,13 +249,9 @@ export const softDeletePaySchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['orderId'], - properties: { - orderId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, diff --git a/src/DTO/preOrder.dto.ts b/src/DTO/preOrder.dto.ts index 5b6dacf..ee1665f 100644 --- a/src/DTO/preOrder.dto.ts +++ b/src/DTO/preOrder.dto.ts @@ -52,11 +52,10 @@ export const newPreOrderSchema = { }, }, ...errorSchema( - E.NotFoundError, E.UserAuthorizationError, E.StoreAuthorizationError, E.NoAuthorizationInHeaderError, - E.NotCorrectTypeError + E.ValidationError ), }, } as const; @@ -128,13 +127,9 @@ export const softDeletePreOrderSchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['preOrderId'], - properties: { - preOrderId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, diff --git a/src/DTO/stock.dto.ts b/src/DTO/stock.dto.ts index 86973fd..8f5cc5d 100644 --- a/src/DTO/stock.dto.ts +++ b/src/DTO/stock.dto.ts @@ -86,13 +86,9 @@ export const softDeleteStockSchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['stockId'], - properties: { - stockId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, @@ -284,13 +280,9 @@ export const softDeleteMixedStockSchema = { }, }, response: { - 200: { - type: 'object', + 204: { + type: 'null', description: 'success response', - required: ['mixedStockId'], - properties: { - mixedStockId: { type: 'number' }, - }, }, ...errorSchema( E.NotFoundError, diff --git a/src/api/hooks/checkStoreIdUser.ts b/src/api/hooks/checkStoreIdUser.ts index b9c9c3b..39d7d8e 100644 --- a/src/api/hooks/checkStoreIdUser.ts +++ b/src/api/hooks/checkStoreIdUser.ts @@ -19,7 +19,6 @@ export default async ( } if (!storeid) { throw new NoAuthorizationInHeaderError('헤더에 storeid가 없습니다'); - // 해당 에러는 test 중 orderService.test.ts 에서 테스트 함. } const replace_authorization = authorization.replace('Bearer ', ''); const userId = LoginToken.getUserId(replace_authorization); diff --git a/src/api/hooks/onError.ts b/src/api/hooks/onError.ts index d4c6033..04eaa93 100644 --- a/src/api/hooks/onError.ts +++ b/src/api/hooks/onError.ts @@ -1,5 +1,6 @@ import { FastifyRequest, FastifyReply, FastifyError } from 'fastify'; import { ErrorWithToast, ValidationError } from '@errors'; +import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; import ErrorConfig from '@errors/config'; import * as Sentry from "@sentry/node"; @@ -16,6 +17,14 @@ export default ( )!.toast(error); return reply.code(400).send(error); } + if(error as FastifyError & { toast?: string } instanceof PrismaClientKnownRequestError) { + if(error.code === 'P2025') { + error.toast = '찾을 수 없는 데이터가 포함되어 있습니다.'; + return reply.code(404).send(error); + } + error.toast = '잘못된 데이터가 입력되었습니다.'; + return reply.code(400).send(error); + } error.toast = '알 수 없는 에러가 발생했습니다.'; return reply.code(500).send(error); } @@ -27,6 +36,7 @@ export default ( .code(knownError.code) .send(error.setToast(knownError.toast(error))); } + reply.code(500).send(); // test 폴더에 있는 hookTest.test.ts 파일에서 test // test 이름은 human error diff --git a/src/api/routes/menu.ts b/src/api/routes/menu.ts index f0e375c..2b2f339 100644 --- a/src/api/routes/menu.ts +++ b/src/api/routes/menu.ts @@ -65,11 +65,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await menuService.softDeleteCategory( + await menuService.softDeleteCategory( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); @@ -107,11 +107,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await menuService.softDeleteMenu( + await menuService.softDeleteMenu( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); @@ -147,11 +147,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await menuService.softDeleteOption( + await menuService.softDeleteOption( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); }; diff --git a/src/api/routes/order.ts b/src/api/routes/order.ts index cd3ed5b..2d72712 100644 --- a/src/api/routes/order.ts +++ b/src/api/routes/order.ts @@ -52,8 +52,8 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await orderService.softDeletePay(request.body, request.params); - reply.code(200).send(result); + await orderService.softDeletePay(request.body, request.params); + reply.code(204).send(); } ); diff --git a/src/api/routes/preOrder.ts b/src/api/routes/preOrder.ts index 743cfd7..b73c472 100644 --- a/src/api/routes/preOrder.ts +++ b/src/api/routes/preOrder.ts @@ -55,11 +55,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await preOrderService.softDeletePreOrder( + await preOrderService.softDeletePreOrder( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); diff --git a/src/api/routes/stock.ts b/src/api/routes/stock.ts index 9872fa5..30ce06f 100644 --- a/src/api/routes/stock.ts +++ b/src/api/routes/stock.ts @@ -39,11 +39,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await stockService.softDeleteStock( + await stockService.softDeleteStock( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); @@ -107,11 +107,11 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { preValidation: checkStoreIdUser, }, async (request, reply) => { - const result = await stockService.softDeleteMixedStock( + await stockService.softDeleteMixedStock( request.body, request.params ); - reply.code(200).send(result); + reply.code(204).send(); } ); diff --git a/src/api/routes/store.ts b/src/api/routes/store.ts index 0905b4f..92f638f 100644 --- a/src/api/routes/store.ts +++ b/src/api/routes/store.ts @@ -16,6 +16,7 @@ const api: FastifyPluginAsync = async (server: FastifyInstance) => { async (request, reply) => { const result = await storeService.newStore(request.body); await storeService.registDefaultOption(result); + await storeService.exampleSeeding(result); reply.code(200).send(result); } ); diff --git a/src/errors/config.ts b/src/errors/config.ts index 3ab7cf9..7a319e1 100644 --- a/src/errors/config.ts +++ b/src/errors/config.ts @@ -54,13 +54,19 @@ const ErrorConfig: ErrorConfigType = [ describtion: '잔액이 부족합니다.', error: E.NotEnoughError, code: 403, - toast: (error: E.NotEnoughError) => `잔액이 부족합니다.`, + toast: (error: E.ErrorWithToast) => `잔액이 부족합니다.`, }, { describtion: '토큰이 유효하지 않습니다.', error: E.UncorrectTokenError, code: 403, - toast: (error: E.UncorrectTokenError) => '토큰이 유효하지 않습니다.', + toast: (error: E.ErrorWithToast) => '토큰이 유효하지 않습니다.', }, + { + describtion: '이미 결제된 주문입니다.', + error: E.AlreadyPaidError, + code: 403, + toast: (error: E.ErrorWithToast) => '이미 결제된 주문입니다.', + } ]; export default ErrorConfig; diff --git a/src/errors/index.ts b/src/errors/index.ts index 1386b7e..06e571e 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -45,26 +45,13 @@ export class ExistError extends ErrorWithToast { } } -export class NotEnoughError extends ErrorWithToast { - constructor(message: string) { - super(message); - this.name = 'NotEnoughError'; - } -} +export const AlreadyPaidError = generateGenericError('alreadyPaidError'); -export class UncorrectTokenError extends ErrorWithToast { - constructor(message: string) { - super(message); - this.name = 'UncorrectTokenError'; - } -} +export const NotEnoughError = generateGenericError('NotEnoughError'); -export class NotDefinedOnConfigError extends ErrorWithToast { - constructor(message: string) { - super(message); - this.name = 'NotDefinedOnConfigError'; - } -} +export const UncorrectTokenError = generateGenericError('UncorrectTokenError'); + +export const NotDefinedOnConfigError = generateGenericError("NotDefinedOnConfigError"); export const ValidationError = generateGenericError('ValidationError'); diff --git a/src/services/menuService.ts b/src/services/menuService.ts index ac208b5..b8b0b4e 100644 --- a/src/services/menuService.ts +++ b/src/services/menuService.ts @@ -1,7 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { NotFoundError } from '@errors'; import * as Menu from '@DTO/menu.dto'; - +import { STATUS, getStockStatus } from '@utils/stockStatus'; const prisma = new PrismaClient(); export default { @@ -27,6 +27,20 @@ export default { include: { optionMenu: true, recipes: { + where: { + OR: [ + { + stock: { + deletedAt: null, + }, + }, + { + mixedStock: { + deletedAt: null, + }, + }, + ], + }, include: { stock: true, mixedStock: { @@ -35,6 +49,11 @@ export default { select: { stock: true, }, + where: { + stock: { + deletedAt: null, + }, + }, } }, } @@ -51,15 +70,10 @@ export default { const menus = category.menu.map((menu) => { const usingStocks = menu.recipes.filter(({ stock }) => stock!==null).map(({ stock }) => stock!); const usingStocksInMixedStocks = menu.recipes.filter(({ mixedStock }) => mixedStock!==null).flatMap(({ mixedStock }) => mixedStock!.mixings.map(({ stock }) => stock)); - const STATUS = ['UNKNOWN', 'EMPTY', 'OUT_OF_STOCK', 'CAUTION', 'ENOUGH'] as const; - const STATUS_ENUM = STATUS.reduce((acc, cur, idx) => ({ ...acc, [cur]: idx }), {} as Record); - const stockStatuses = usingStocks.concat(usingStocksInMixedStocks).map(({ currentAmount, noticeThreshold }) => { - if (currentAmount === null) return STATUS_ENUM['UNKNOWN']; - if (currentAmount < noticeThreshold * 0.1) return STATUS_ENUM['EMPTY']; - if (currentAmount < noticeThreshold * 0.3) return STATUS_ENUM['OUT_OF_STOCK']; - if (currentAmount < noticeThreshold) return STATUS_ENUM['CAUTION']; - return STATUS_ENUM['ENOUGH']; - }); + const stockStatuses = usingStocks + .concat(usingStocksInMixedStocks) + .filter(({noticeThreshold})=>noticeThreshold>=0) + .map(({ currentAmount, noticeThreshold }) => getStockStatus(currentAmount, noticeThreshold)); return ({ id: menu.id, name: menu.name, @@ -86,25 +100,44 @@ export default { deletedAt: null, }, include: { - optionMenu: true, + optionMenu: { + where: { + option:{ + deletedAt: null, + } + } + }, category: true, recipes: { include: { stock: true, mixedStock: true, }, + where: { + OR: [ + { + stock: { + deletedAt: null, + }, + }, + { + mixedStock: { + deletedAt: null, + }, + }, + ], + }, }, }, }); if (!menu) { - // 해당 에러는 test 중 menuService.test.ts 에서 테스트 함. - // test 이름은 get not exist menu detail throw new NotFoundError('메뉴가 존재하지 않습니다.', '메뉴'); } const allOption = await prisma.option.findMany({ where: { storeId, + deletedAt: null, }, }); @@ -215,16 +248,31 @@ export default { }: Menu.updateOptionInterface['Body']): Promise< Menu.updateOptionInterface['Reply']['201'] > { - const result = await prisma.option.update({ + const { optionMenu: preOptionMenu } = await prisma.option.update({ where: { id: optionId, storeId, }, data: { + deletedAt: new Date(), + }, + include: { + optionMenu: true, + }, + }); + const result = await prisma.option.create({ + data: { + storeId, optionName, optionPrice, optionCategory, - id: optionId, + optionMenu: { + createMany: { + data: preOptionMenu.map(({ menuId }) => ({ + menuId, + })), + } + } }, }); return { @@ -280,7 +328,7 @@ export default { async softDeleteCategory( { storeId }: Menu.softDeleteCategoryInterface['Body'], { categoryId }: Menu.softDeleteCategoryInterface['Params'] - ): Promise { + ): Promise { await prisma.category.update({ where: { id: categoryId, @@ -290,7 +338,15 @@ export default { deletedAt: new Date(), }, }); - return { categoryId }; + await prisma.menu.updateMany({ + where: { + categoryId, + storeId, + }, + data: { + deletedAt: new Date(), + }, + }); }, async createMenu({ @@ -465,8 +521,6 @@ export default { }, }); - if (!option) option = []; - const optionMenuIds = softDeleteMenu.optionMenu .map(({ optionId }) => optionId) .sort(); @@ -526,7 +580,7 @@ export default { async softDeleteMenu( { storeId }: Menu.softDeleteMenuInterface['Body'], { menuId }: Menu.softDeleteMenuInterface['Params'] - ): Promise { + ): Promise { await prisma.menu.update({ where: { id: menuId, @@ -536,13 +590,12 @@ export default { deletedAt: new Date(), }, }); - return { menuId }; }, async softDeleteOption( { storeId }: Menu.softDeleteOptionInterface['Body'], { optionId }: Menu.softDeleteOptionInterface['Params'] - ): Promise { + ): Promise { await prisma.option.update({ where: { id: optionId, @@ -552,6 +605,5 @@ export default { deletedAt: new Date(), }, }); - return { optionId }; }, }; diff --git a/src/services/orderService.ts b/src/services/orderService.ts index d21ecf4..8c26daa 100644 --- a/src/services/orderService.ts +++ b/src/services/orderService.ts @@ -1,6 +1,6 @@ import { Prisma, PrismaClient } from '@prisma/client'; import * as Order from '@DTO/order.dto'; -import { NotFoundError, NotCorrectTypeError, NotEnoughError } from '@errors'; +import { NotFoundError, NotCorrectTypeError, NotEnoughError, AlreadyPaidError } from '@errors'; const prisma = new PrismaClient(); export default { @@ -56,14 +56,10 @@ export default { }, }); if (order === null) { - // orderService.test 에서 test - // test 이름은 pay not exist order throw new NotFoundError('해당하는 주문이 없습니다.', '주문'); } if (order.paymentStatus !== 'WAITING') { - // orderService.test 에서 test - // test 이름은 pay again - throw new NotFoundError('이미 결제된 주문입니다.', '결제 예정 주문'); + throw new AlreadyPaidError('이미 결제된 주문입니다.'); } if (mileageId !== undefined && mileageId !== null) { @@ -73,8 +69,6 @@ export default { }, }); if (mileage === null) { - // orderService.test 에서 test - // test 이름은 pay with not exist mileage throw new NotFoundError('해당하는 마일리지가 없습니다.', '마일리지'); } if ( @@ -83,8 +77,6 @@ export default { useMileage === null || saveMileage === null ) { - // orderService.test 에서 test - // test 이름은 pay without useMileage and saveMileage throw new NotCorrectTypeError( '사용할 마일리지와 적립할 마일리지를 입력해주세요.', '사용할 마일리지와 적립할 마일리지' @@ -231,8 +223,6 @@ export default { }, }); if (order === null) { - // orderService.test 에서 test - // test 이름은 get not exist order throw new NotFoundError('해당하는 주문이 없습니다.', '주문'); } @@ -254,7 +244,7 @@ export default { })), }; }); - const pay = { + const pay = paymentStatus === 'WAITING' ? undefined :{ paymentMethod: order.payment[0].paymentMethod as 'CARD' | 'CASH' | 'BANK', price: order.payment[0].price.toString(), }; @@ -281,7 +271,7 @@ export default { async softDeletePay( { storeId }: Order.softDeletePayInterface['Body'], { orderId }: Order.softDeletePayInterface['Params'] - ): Promise { + ): Promise { const order = await prisma.order.findUnique({ where: { id: orderId, @@ -294,26 +284,24 @@ export default { if (order === null) { throw new NotFoundError('해당하는 주문이 없습니다.', '주문'); } - if (order.paymentStatus !== 'PAID') { - throw new NotFoundError('결제되지 않은 주문입니다.', '결제된 주문'); + if (order.paymentStatus == 'PAID'){ + await prisma.order.update({ + where: { + id: orderId, + }, + data: { + paymentStatus: 'CANCELED', + deletedAt: new Date(), + }, + }); } - await prisma.order.update({ - where: { - id: orderId, - }, - data: { - paymentStatus: 'CANCELED', - deletedAt: new Date(), - }, - }); - return { orderId }; }, async getOrderList( { storeId }: Order.getOrderListInterface['Body'], { page, count, date }: Order.getOrderListInterface['Querystring'] ): Promise { - date = date ?? new Date().toISOString().split('T')[0]; + date = date ?? new Date().toISOString(); const reservationDate = new Date(date); const krDate = new Date(reservationDate.getTime() + 9 * 60 * 60 * 1000); const krDateStr = new Date(krDate.toISOString().split('T')[0]); diff --git a/src/services/preOrderService.ts b/src/services/preOrderService.ts index ed7641b..0bc5d63 100644 --- a/src/services/preOrderService.ts +++ b/src/services/preOrderService.ts @@ -53,11 +53,17 @@ export default { }: PreOrder.updatePreOrderInterface['Body']): Promise< PreOrder.updatePreOrderInterface['Reply']['201'] > { - const result = await prisma.preOrder.update({ + await prisma.preOrder.update({ where: { id, storeId, }, + data: { + deletedAt: new Date(), + } + }); + + const result = await prisma.preOrder.create({ data: { totalPrice, phone, @@ -88,25 +94,16 @@ export default { async softDeletePreOrder( { storeId }: PreOrder.softDeletePreOrderInterface['Body'], { preOrderId }: PreOrder.softDeletePreOrderInterface['Params'] - ): Promise { - const preOrder = await prisma.preOrder.findUnique({ - where: { - id: preOrderId, - storeId, - }, - }); - if (preOrder === null) { - throw new NotFoundError('해당하는 예약 주문이 없습니다.', '예약 주문'); - } + ): Promise { await prisma.preOrder.update({ where: { id: preOrderId, + storeId, }, data: { deletedAt: new Date(), }, }); - return { preOrderId }; }, async getPreOrder( @@ -173,14 +170,13 @@ export default { { storeId }: PreOrder.getPreOrderListInterface['Body'], { page, count, date }: PreOrder.getPreOrderListInterface['Querystring'] ): Promise { - date = date ?? new Date().toISOString().split('T')[0]; + date = date ?? new Date().toISOString(); const reservationDate = new Date(date); const krDate = new Date(reservationDate.getTime() + 9 * 60 * 60 * 1000); const krDateStr = new Date(krDate.toISOString().split('T')[0]); const krDateEnd = new Date(krDateStr.getTime() + 24 * 60 * 60 * 1000); const utcDateStr = new Date(krDateStr.getTime() - 9 * 60 * 60 * 1000); const utcDateEnd = new Date(krDateEnd.getTime() - 9 * 60 * 60 * 1000); - const [preOrders, totalPreOrderCount] = await Promise.all([ prisma.preOrder.findMany({ where: { diff --git a/src/services/stockService.ts b/src/services/stockService.ts index 57f4c0d..f93a89e 100644 --- a/src/services/stockService.ts +++ b/src/services/stockService.ts @@ -1,6 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { NotFoundError } from '@errors'; import * as Stock from '@DTO/stock.dto'; +import { STATUS, getStockStatus } from '@utils/stockStatus'; const prisma = new PrismaClient(); @@ -88,6 +89,11 @@ export default { }, }, mixings: { + where:{ + mixedStock: { + deletedAt: null, + } + }, select: { mixedStock: { select: { @@ -112,16 +118,7 @@ export default { return { stocks: result.map( ({ id, name, currentAmount, noticeThreshold, _count, mixings }) => { - const status = (( - currentAmount: number | null, - noticeThreshold: number - ) => { - if (currentAmount === null) return 'UNKNOWN'; - if (currentAmount < noticeThreshold * 0.1) return 'EMPTY'; - if (currentAmount < noticeThreshold * 0.3) return 'OUT_OF_STOCK'; - if (currentAmount < noticeThreshold) return 'CAUTION'; - return 'ENOUGH'; - })(currentAmount, noticeThreshold); + const status = STATUS[getStockStatus(currentAmount, noticeThreshold)]; return { id, @@ -149,7 +146,7 @@ export default { async softDeleteStock( { storeId }: Stock.softDeleteStockInterface['Body'], { stockId }: Stock.softDeleteStockInterface['Params'] - ): Promise { + ): Promise { await prisma.stock.update({ where: { id: stockId, @@ -159,8 +156,6 @@ export default { deletedAt: new Date(), }, }); - - return { stockId }; }, async getStock( @@ -180,7 +175,7 @@ export default { return { name: result.name, - price: result.price === null ? '0' : result.price.toString(), + price: result.price === null ? null : result.price.toString(), amount: result.amount, currentAmount: result.currentAmount, noticeThreshold: result.noticeThreshold, @@ -308,7 +303,7 @@ export default { async softDeleteMixedStock( { storeId }: Stock.softDeleteMixedStockInterface['Body'], { mixedStockId }: Stock.softDeleteMixedStockInterface['Params'] - ): Promise { + ): Promise { await prisma.mixedStock.update({ where: { id: mixedStockId, @@ -318,8 +313,6 @@ export default { deletedAt: new Date(), }, }); - - return { mixedStockId }; }, async getMixedStockList({ @@ -357,6 +350,11 @@ export default { include: { stock: true, }, + where: { + stock: { + deletedAt: null, + }, + } }, }, }); diff --git a/src/services/storeService.ts b/src/services/storeService.ts index b6483bf..b359397 100644 --- a/src/services/storeService.ts +++ b/src/services/storeService.ts @@ -1,6 +1,7 @@ import { PrismaClient } from '@prisma/client'; import * as Store from '@DTO/store.dto'; -import menuSeed from '../../prisma/menuSeed'; +import testSeed from '@seeding/testSeed'; +import defaultSeed from '@seeding/defaultSeed'; const prisma = new PrismaClient(); export default { @@ -42,12 +43,12 @@ export default { optionCategory: '온도', }, { - optionName: '케냐', + optionName: '원두1', optionPrice: 0, optionCategory: '원두', }, { - optionName: '콜롬비아', + optionName: '원두2', optionPrice: 300, optionCategory: '원두', }, @@ -57,7 +58,7 @@ export default { optionCategory: '샷', }, { - optionName: '연하게', + optionName: '샷 빼기', optionPrice: 0, optionCategory: '샷', }, @@ -68,8 +69,12 @@ export default { }); }, + async exampleSeeding({ storeId }: { storeId: number }): Promise { + await defaultSeed(prisma, storeId); + }, + async seeding({ storeId }: { storeId: number }): Promise { - await menuSeed(prisma, storeId); + await testSeed(prisma, storeId); }, async getStoreList({ diff --git a/src/services/userService.ts b/src/services/userService.ts index 82f80f2..5a258aa 100644 --- a/src/services/userService.ts +++ b/src/services/userService.ts @@ -23,6 +23,7 @@ export default { throw new E.UserAuthorizationError('휴대폰 번호가 유효하지 않습니다.'); } let certificationCode = '123456'; + /* istanbul ignore next */ if(process.env.NODE_ENV === 'production'){ certificationCode = crypto.randomInt(100000, 999999).toString(); sendSMS(phone, 'SMS', `[카페포스] 인증번호는 ${certificationCode}입니다.`); diff --git a/src/utils/stockStatus.ts b/src/utils/stockStatus.ts new file mode 100644 index 0000000..30d690f --- /dev/null +++ b/src/utils/stockStatus.ts @@ -0,0 +1,20 @@ +export const STATUS = ['UNKNOWN', 'EMPTY', 'OUT_OF_STOCK', 'CAUTION', 'ENOUGH'] as const; +const enum STATUS_ENUM { + UNKNOWN, + EMPTY, + OUT_OF_STOCK, + CAUTION, + ENOUGH, +} + +export const getStockStatus = ( + currentAmount: number | null, + noticeThreshold: number + ) => { + if (noticeThreshold < 0) return STATUS_ENUM.UNKNOWN; + if (currentAmount === null) return STATUS_ENUM.UNKNOWN; + if (currentAmount < noticeThreshold * 0.1) return STATUS_ENUM.EMPTY; + if (currentAmount < noticeThreshold * 0.3) return STATUS_ENUM.OUT_OF_STOCK; + if (currentAmount < noticeThreshold) return STATUS_ENUM.CAUTION; + return STATUS_ENUM.ENOUGH; + } diff --git a/test/getAccessToken.ts b/test/getAccessToken.ts index 4cd586b..85d27f5 100644 --- a/test/getAccessToken.ts +++ b/test/getAccessToken.ts @@ -1,6 +1,3 @@ import { LoginToken } from '@utils/jwt'; const userId = Number(process.argv[2]); - -console.log(process.argv); - console.log(new LoginToken(userId).signAccessToken()); diff --git a/test/integration/0. init/basic.ts b/test/integration/0. init/basic.ts new file mode 100644 index 0000000..264e52f --- /dev/null +++ b/test/integration/0. init/basic.ts @@ -0,0 +1,49 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('ping', async () => { + const response = await app.inject({ + method: 'GET', + url: '/api/ping', + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as { data: string }; + expect(body.data).toBe('pong'); + }); + + test('notDefinederror', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/notDefinederror', + }); + expect(response.statusCode).toBe(500); + }); + + test('human error', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/notDefinedOnConfigerror', + }); + expect(response.statusCode).toBe(500); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('notDefinedOnConfigerror'); + }); + + test('notFound', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/notFound', + }); + expect(response.statusCode).toBe(404); + }); + + test('400', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/phone', + }); + expect(response.statusCode).toBe(400); + }); +}; diff --git a/test/integration/0. init/getBeforeRegister.ts b/test/integration/0. init/getBeforeRegister.ts new file mode 100644 index 0000000..eb360fb --- /dev/null +++ b/test/integration/0. init/getBeforeRegister.ts @@ -0,0 +1,279 @@ +import { FastifyInstance } from 'fastify'; +import testValues from '../testValues'; +import * as Menu from '@DTO/menu.dto'; +import * as Stock from '@DTO/stock.dto'; +import * as Order from '@DTO/order.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('get stock list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + expect(body.stocks).toEqual( + [ + { + id: testValues.waterId, + name: '물', + status: 'UNKNOWN', + usingMenuCount: 1, + }, + { + id: testValues.coffeeBeanId, + name: '원두(예시)', + status: 'ENOUGH', + usingMenuCount: 2, + }, + { + id: testValues.milkId, + name: '우유(예시)', + status: 'CAUTION', + usingMenuCount: 1, + } + ] + ); + }); + + test('get stock detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/${testValues.waterId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockInterface['Reply']['200']; + expect(body).toEqual( + { + name: '물', + amount: null, + unit: 'ml', + price: null, + currentAmount: null, + noticeThreshold: -1, + updatedAt: expect.any(String), + } + ); + }); + + test('get stock detail: fail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/9999999999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('get mixed stock list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getMixedStockListInterface['Reply']['200']; + expect(body.mixedStocks).toEqual([]); + }); + + test('get mixed stock detail: fail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed/9999999999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('get menu list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + expect(body.categories).toEqual([{ + categoryId: 1, + category: '커피', + menus: [ + { + id: testValues.americanoId, + name: '아메리카노(예시)', + price: "2500", + stockStatus: 'ENOUGH', + }, + { + id: testValues.latteId, + name: '카페라떼(예시)', + price: "3000", + stockStatus: 'CAUTION', + } + ] + }]); + }); + + test('get menu detail: fail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/9999999999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('get menu detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.americanoId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuInterface['Reply']['200']; + expect(body).toEqual( + { + name: '아메리카노(예시)', + price: "2500", + category: '커피', + categoryId: testValues.coffeeCategoryId, + option: [ + { + optionType: "온도", + options: [ + { + id: testValues.iceOptionId, + name: 'ice', + price: "0", + isSelectable: true, + }, + { + id: testValues.hotOptionId, + name: 'hot', + price: "0", + isSelectable: true, + } + ] + }, + { + optionType: "원두", + options: [ + { + id: testValues.bean1OptionId, + name: '원두1', + price: "0", + isSelectable: true, + }, + { + id: testValues.bean2OptionId, + name: '원두2', + price: "300", + isSelectable: true, + } + ] + }, + { + optionType: "샷", + options: [ + { + id: testValues.shotPlusOptionId, + name: '1샷 추가', + price: "500", + isSelectable: true, + }, + { + id: testValues.shotMinusOptionId, + name: '샷 빼기', + price: "0", + isSelectable: true, + } + ] + } + ], + recipe: [ + { + id: testValues.waterId, + name: '물', + coldRegularAmount: 150, + coldSizeUpAmount: null, + hotRegularAmount: null, + hotSizeUpAmount: null, + isMixed: false, + unit: 'ml', + }, + { + id: testValues.coffeeBeanId, + name: '원두(예시)', + coldRegularAmount: 25, + coldSizeUpAmount: null, + hotRegularAmount: null, + hotSizeUpAmount: null, + isMixed: false, + unit: 'g', + }, + ], + } + ); + }); + + test('default options', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/option`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getOptionListInterface['Reply']['200']; + expect(body.option).toEqual([ + { + optionType: '온도', + options: [ + { + id: expect.any(Number), + name: 'ice', + price: '0', + }, + { + id: expect.any(Number), + name: 'hot', + price: '0', + }, + ], + }, + { + optionType: '원두', + options: [ + { + id: expect.any(Number), + name: '원두1', + price: '0', + }, + { + id: expect.any(Number), + name: '원두2', + price: '300', + }, + ], + }, + { + optionType: '샷', + options: [ + { + id: expect.any(Number), + name: '1샷 추가', + price: '500', + }, + { + id: expect.any(Number), + name: '샷 빼기', + price: '0', + }, + ], + }, + ]); + }); + + +} diff --git a/test/integration/0. init/hook.ts b/test/integration/0. init/hook.ts new file mode 100644 index 0000000..fee5cc8 --- /dev/null +++ b/test/integration/0. init/hook.ts @@ -0,0 +1,73 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import { LoginToken } from '@utils/jwt'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + const accessToken = new LoginToken(1).signAccessToken(); + + test('checkUser: without authorization header', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/store`, + }); + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('헤더에 Authorization이 없습니다'); + }); + + test('checkStoreIdUser: without authorization header', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: { + storeid: '1', + }, + }); + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('헤더에 Authorization이 없습니다'); + }); + + test('checkStoreIdUser: without storeid header', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: { + authorization: `Bearer ${accessToken}`, + }, + }); + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('헤더에 storeid가 없습니다'); + }); + + test('checkStoreIdUser: without storeid header', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: { + authorization: `Bearer ${accessToken}1`, + storeid: '1', + }, + }); + expect(response.statusCode).toBe(401); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('유저 인증에 실패했습니다'); + }); + + test('checkStoreIdUser: without storeid header', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: { + authorization: `Bearer ${accessToken}`, + storeid: '1', + }, + }); + expect(response.statusCode).toBe(401); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('가게 인증에 실패했습니다'); + }); + +}; diff --git a/test/integration/0. init/index.ts b/test/integration/0. init/index.ts new file mode 100644 index 0000000..1e51c66 --- /dev/null +++ b/test/integration/0. init/index.ts @@ -0,0 +1,22 @@ +import { FastifyInstance } from 'fastify'; +import { describe } from '@jest/globals'; +import basic from './basic'; +import hook from './hook'; +import login from './login'; +import store from './store'; +import getBeforeRegister from './getBeforeRegister'; + + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['basic', basic], + ['hook', hook], + ['login', login], + ['store CRU', store], + ['get stock, mixed stock, menu, mileage before register', getBeforeRegister] +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/0. init/login.ts b/test/integration/0. init/login.ts new file mode 100644 index 0000000..a1ab7a1 --- /dev/null +++ b/test/integration/0. init/login.ts @@ -0,0 +1,161 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import { expect, test } from '@jest/globals'; +import * as User from '@DTO/user.dto'; + +export default (app: FastifyInstance) => () => { + const phone = '01012345678'; + const businessRegistrationNumber = '5133001104'; + let tokenForCertificatePhone: string; + let certificatedPhoneToken: string; + let refreshToken: string; + + test('send CertificationCode', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/phone', + payload: { + phone: phone, + }, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as User.phoneInterface['Reply']['200']; + expect(body).toHaveProperty('tokenForCertificatePhone'); + tokenForCertificatePhone = body.tokenForCertificatePhone as string; + }); + + test('send CertificationCode: fail', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/phone', + payload: { + phone: '010esrdfsd', + }, + }); + expect(response.statusCode).toBe(401); + }); + + test('certificate phone:success', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/phone/certificationCode', + payload: { + phone: phone, + certificationCode: '123456', + phoneCertificationToken: tokenForCertificatePhone, + }, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as User.certificatePhoneInterface['Reply']['200']; + expect(body).toHaveProperty('certificatedPhoneToken'); + certificatedPhoneToken = body.certificatedPhoneToken; + }); + + test('certificate phone:fail', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/phone/certificationCode', + payload: { + phone: phone, + certificationCode: '111222', + phoneCertificationToken: tokenForCertificatePhone, + }, + }); + expect(response.statusCode).toBe(401); + }); + + test('new user: fail because of invalid businessRegistrationNumber', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/login', + payload: { + businessRegistrationNumber: 'asdf', + certificatedPhoneToken: certificatedPhoneToken, + }, + }); + expect(response.statusCode).toBe(401); + }); + + test('new user: fail because of invalid certificatedPhoneToken', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/login', + payload: { + businessRegistrationNumber: businessRegistrationNumber, + certificatedPhoneToken: 'asdf', + }, + }); + expect(response.statusCode).toBe(401); + }); + + test('new user', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/login', + payload: { + businessRegistrationNumber: businessRegistrationNumber, + certificatedPhoneToken: certificatedPhoneToken, + }, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as User.loginInterface['Reply']['200']; + expect(body).toHaveProperty('accessToken'); + expect(body).toHaveProperty('refreshToken'); + }); + + test('login', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/login', + payload: { + businessRegistrationNumber: businessRegistrationNumber, + certificatedPhoneToken: certificatedPhoneToken, + }, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as User.loginInterface['Reply']['200']; + expect(body).toHaveProperty('accessToken'); + expect(body).toHaveProperty('refreshToken'); + refreshToken = body.refreshToken; + }); + + test('refresh token:fail', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/refresh', + headers: { + Authorization: `Bearer ${refreshToken + 1}`, + }, + }); + expect(response.statusCode).toBe(401); + }); + + test('refresh token', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/user/refresh', + headers: { + Authorization: `Bearer ${refreshToken}`, + }, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as User.refreshInterface['Reply']['200']; + expect(body).toHaveProperty('accessToken'); + expect(body).toHaveProperty('refreshToken'); + }); +} diff --git a/test/integration/0. init/store.ts b/test/integration/0. init/store.ts new file mode 100644 index 0000000..fd495fe --- /dev/null +++ b/test/integration/0. init/store.ts @@ -0,0 +1,136 @@ +import { FastifyInstance } from 'fastify'; +import testValues from '../testValues'; +import * as Store from '@DTO/store.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + const storeName = '소예다방'; + const storeAddress = '서울시 강남구'; + const defaultOpeningHours: Array<{ + day: string; + open: string | null; + close: string | null; + }> = [ + { + day: '일', + open: null, + close: null, + }, + ...['월', '화', '수', '목', '금', '토'].map((day) => ({ + day: day, + open: '09:00', + close: '18:00', + })), + { + day: '공휴일', + open: '10:00', + close: '17:00', + }, + ]; + + test('new store', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/store', + payload: { + name: "테스트 매장", + address: "테스트 주소", + openingHours: defaultOpeningHours, + }, + headers: testValues.userHeader, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as Store.newStoreInterface['Reply']['200']; + expect(body).toHaveProperty('storeId'); + }); + + test('get store list', async () => { + const response = await app.inject({ + method: 'GET', + url: '/api/store', + headers: testValues.userHeader, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as Store.storeListInterface['Reply']['200']; + expect(body).toHaveProperty('stores'); + expect(body.stores.length).toEqual(1); + const lastStore = body.stores[0]; + expect(lastStore).toEqual({ + storeId: 1, + name: "테스트 매장", + address: "테스트 주소", + }); + }); + + test('update store', async () => { + const response = await app.inject({ + method: 'PUT', + url: '/api/store', + payload: { + name: storeName, + address: storeAddress, + openingHours: defaultOpeningHours, + }, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(201); + + const body = JSON.parse( + response.body + ) as Store.updateStoreInterface['Reply']['201']; + expect(body).toHaveProperty('storeId'); + expect(body.storeId).toBe(1); + }); + + test('register test store', async () => { + const response = await app.inject({ + method: 'POST', + url: '/api/store/test', + payload: { + name: '테스트 매장', + address: '테스트 주소', + openingHours: defaultOpeningHours, + }, + headers: testValues.userHeader, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as Store.newStoreInterface['Reply']['200']; + expect(body).toHaveProperty('storeId'); + }); + + test('get store list', async () => { + const response = await app.inject({ + method: 'GET', + url: '/api/store', + headers: testValues.userHeader, + }); + expect(response.statusCode).toBe(200); + + const body = JSON.parse( + response.body + ) as Store.storeListInterface['Reply']['200']; + expect(body).toHaveProperty('stores'); + expect(body.stores.length).toEqual(2); + expect(body.stores).toEqual([ + { + storeId: 1, + name: storeName, + address: storeAddress, + }, + { + storeId: 2, + name: "테스트 매장", + address: "테스트 주소", + }, + ]); + }); +} diff --git a/test/integration/0. init/testTemplate.ts b/test/integration/0. init/testTemplate.ts new file mode 100644 index 0000000..0596f26 --- /dev/null +++ b/test/integration/0. init/testTemplate.ts @@ -0,0 +1,9 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { +}; diff --git a/test/integration/1. register/index.ts b/test/integration/1. register/index.ts new file mode 100644 index 0000000..76f35ed --- /dev/null +++ b/test/integration/1. register/index.ts @@ -0,0 +1,19 @@ +import { FastifyInstance } from 'fastify'; +import { afterAll, describe} from '@jest/globals'; +import stock from './stock'; +import mixedStock from './mixedStock'; +import menu from './menu'; +import mileage from './mileage'; + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['register stock', stock], + ['register mixed stock', mixedStock], + ['register menu', menu], + ['register mileage', mileage], +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/1. register/menu.ts b/test/integration/1. register/menu.ts new file mode 100644 index 0000000..0f73b8f --- /dev/null +++ b/test/integration/1. register/menu.ts @@ -0,0 +1,161 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('register grapefruit ade:fail non-exist category', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/menu', + headers: testValues.storeHeader, + payload: { + name: '자몽에이드', + price: 6000, + categoryId: testValues.adeCategoryId, + option: [1,3,4], + recipe: [ + { + id: testValues.preservedGrapefruitId, + isMixed: true, + unit: 'g', + coldRegularAmount: 50, + }, + { + id: testValues.sparklingWaterId, + isMixed: false, + unit: 'ml', + coldRegularAmount: 150, + }, + ], + }, + }); + expect(res.statusCode).toEqual(400); + }); + + test('register category:ade', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/menu/category', + headers: testValues.storeHeader, + payload: { + name: '에이드', + }, + }); + const data = JSON.parse(res.body) as Menu.createCategoryInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('categoryId'); + testValues.setValues('adeCategoryId', data.categoryId); + }); + + test('register grapefruit ade:fail non-exist stock', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/menu', + headers: testValues.storeHeader, + payload: { + name: '자몽에이드', + price: 6000, + categoryId: testValues.adeCategoryId, + option: [1,3,4], + recipe: [ + { + id: testValues.preservedGrapefruitId, + isMixed: true, + unit: 'g', + coldRegularAmount: 50, + }, + { + id: 999999, + isMixed: false, + unit: 'ml', + coldRegularAmount: 150, + }, + ], + }, + }); + expect(res.statusCode).toEqual(400); + }); + + test('register grapefruit ade', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/menu', + headers: testValues.storeHeader, + payload: { + name: '자몽에이드', + price: 6000, + categoryId: testValues.adeCategoryId, + option: [1,3,4], + recipe: [ + { + id: testValues.preservedGrapefruitId, + isMixed: true, + unit: 'g', + coldRegularAmount: 50, + }, + { + id: testValues.sparklingWaterId, + isMixed: false, + unit: 'ml', + coldRegularAmount: 150, + }, + ], + }, + }); + const data = JSON.parse(res.body) as Menu.createMenuInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('menuId'); + testValues.setValues('grapefruitAdeId', data.menuId); + }); + + test('register lemon ade', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/menu', + headers: testValues.storeHeader, + payload: { + name: '레몬에이드', + price: 6000, + categoryId: testValues.adeCategoryId, + option: [1,3,4], + recipe: [ + { + id: testValues.preservedLemonId, + isMixed: true, + unit: 'g', + coldRegularAmount: 50, + }, + { + id: testValues.sparklingWaterId, + isMixed: false, + unit: 'ml', + coldRegularAmount: 150, + }, + ], + }, + }); + const data = JSON.parse(res.body) as Menu.createMenuInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('menuId'); + testValues.setValues('lemonAdeId', data.menuId); + }); + + test('stock list after register', async () => { + const res = await app.inject({ + method: 'GET', + url: '/api/stock', + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockListInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('stocks'); + expect(data.stocks).toHaveLength(7); + + const grapefruitStock = data.stocks.find((stock) => stock.name === '자몽'); + expect(grapefruitStock).toBeDefined(); + expect(grapefruitStock!.usingMenuCount).toEqual(1); + }); +} diff --git a/test/integration/1. register/mileage.ts b/test/integration/1. register/mileage.ts new file mode 100644 index 0000000..fc3d8aa --- /dev/null +++ b/test/integration/1. register/mileage.ts @@ -0,0 +1,115 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Mileage from '@DTO/mileage.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('mileage', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/mileage?phone=${testValues.customerPhone1}`, + headers: testValues.storeHeader, + }); + + expect(response.statusCode).toBe(404); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('해당하는 마일리지가 없습니다.'); + expect(body.toast).toBe('마일리지을(를) 찾을 수 없습니다.'); + }); + + test('register mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/mileage`, + headers: testValues.storeHeader, + payload: { + phone: testValues.customerPhone1, + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse( + response.body + ) as Mileage.registerMileageInterface['Reply']['200']; + expect(body.mileageId).toBeDefined(); + testValues.setValues('mileageId1', body.mileageId); + }); + + test('register mileage with not correct phone check', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/mileage`, + headers: testValues.storeHeader, + payload: { + phone: testValues.wrongPhone, + }, + }); + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('입력된 전화번호이(가) 양식과 맞지 않습니다.'); + }); + + test('exist mileage check', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/mileage`, + headers: testValues.storeHeader, + payload: { + phone: testValues.customerPhone1, + }, + }); + expect(response.statusCode).toBe(409); + const body = JSON.parse(response.body) as ErrorInterface; + expect(body.message).toBe('입력된 전화번호가 이미 존재합니다.'); + expect(body.toast).toBe('입력된 전화번호가 이미 존재합니다.'); + }); + + test('add mileage', async () => { + const response = await app.inject({ + method: 'PATCH', + url: `/api/mileage`, + headers: testValues.storeHeader, + payload: { + mileageId: testValues.mileageId1, + mileage: 1000, + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse( + response.body + ) as Mileage.saveMileageInterface['Reply']['200']; + expect(body.mileage).toBe('1000'); + }); + + test('get mileage', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/mileage?phone=${testValues.customerPhone1}`, + headers: testValues.storeHeader, + }); + + expect(response.statusCode).toBe(200); + const body = JSON.parse( + response.body + ) as Mileage.getMileageInterface['Reply']['200']; + expect(body.mileage).toBe('1000'); + expect(body.mileageId).toBe(testValues.mileageId1); + }); + + test('register second mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/mileage`, + headers: testValues.storeHeader, + payload: { + phone: testValues.customerPhone2, + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse( + response.body + ) as Mileage.registerMileageInterface['Reply']['200']; + expect(body.mileageId).toBeDefined(); + testValues.setValues('mileageId2', body.mileageId); + }); +} diff --git a/test/integration/1. register/mixedStock.ts b/test/integration/1. register/mixedStock.ts new file mode 100644 index 0000000..a6bd2c2 --- /dev/null +++ b/test/integration/1. register/mixedStock.ts @@ -0,0 +1,112 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('register preservedGrapefruit', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock/mixed', + headers: testValues.storeHeader, + payload: { + name: '자몽청', + totalAmount: 2000, + unit: 'g', + mixing: [ + { + id: testValues.grapefruitId, + unit: 'g', + amount: 1000, + }, + { + id: testValues.sugarId, + unit: 'g', + amount: 1000, + } + ] + }, + }); + const data = JSON.parse(res.body) as Stock.createMixedStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('mixedStockId'); + testValues.setValues('preservedGrapefruitId', data.mixedStockId); + }); + + test('register preservedLemon', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock/mixed', + headers: testValues.storeHeader, + payload: { + name: '레몬청', + mixing: [ + { + id: testValues.lemonId, + unit: 'g', + amount: 1000, + }, + { + id: testValues.sugarId, + unit: 'g', + amount: 1000, + } + ] + }, + }); + const data = JSON.parse(res.body) as Stock.createMixedStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('mixedStockId'); + testValues.setValues('preservedLemonId', data.mixedStockId); + }); + + test('get mixed stock list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getMixedStockListInterface['Reply']['200']; + expect(body.mixedStocks).toHaveLength(2); + }); + + test('get stock detail:lemon', async () => { + //레몬의 유닛이 업데이트 되었는지 확인 + const res = await app.inject({ + method: 'GET', + url: `/api/stock/${testValues.lemonId}`, + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('name', '레몬'); + expect(data).toHaveProperty('unit', 'g'); + }); + + test('search mixed stock', async () => { + const res = await app.inject({ + method: 'GET', + url: '/api/stock/withMixed/search?name=자몽', + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockListInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('stocks'); + expect(data.stocks).toHaveLength(2); + expect(data.stocks[0]).toHaveProperty('name', '자몽'); + expect(data.stocks[1]).toHaveProperty('name', '자몽청'); + }); + + test('get mixed stock detail:preservedGrapefruit', async () => { + const res = await app.inject({ + method: 'GET', + url: `/api/stock/mixed/${testValues.preservedGrapefruitId}`, + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getMixedStockInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('name', '자몽청'); + }); +} diff --git a/test/integration/1. register/stock.ts b/test/integration/1. register/stock.ts new file mode 100644 index 0000000..2018346 --- /dev/null +++ b/test/integration/1. register/stock.ts @@ -0,0 +1,122 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import * as Stock from '@DTO/stock.dto'; +import testValues from '../testValues'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('register grapefruit', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock', + headers: testValues.storeHeader, + payload: { + name: '자몽', + unit: 'g', + currentAmount: 3000, + noticeThreshold: 500, + amount: 2800, + price: 26900, + }, + }); + const data = JSON.parse(res.body) as Stock.createStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('stockId'); + testValues.setValues('grapefruitId', data.stockId); + }); + + test('register lemon', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock', + headers: testValues.storeHeader, + payload: { + name: '레몬', + }, + }); + const data = JSON.parse(res.body) as Stock.createStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('stockId'); + testValues.setValues('lemonId', data.stockId); + }); + + test('register sparkling water', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock', + headers: testValues.storeHeader, + payload: { + name: '탄산수', + }, + }); + const data = JSON.parse(res.body) as Stock.createStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('stockId'); + testValues.setValues('sparklingWaterId', data.stockId); + }); + + test('register sugar', async () => { + const res = await app.inject({ + method: 'POST', + url: '/api/stock', + headers: testValues.storeHeader, + payload: { + name: '설탕', + unit: 'g', + currentAmount: 3000, + noticeThreshold: 500, + }, + }); + const data = JSON.parse(res.body) as Stock.createStockInterface['Reply']['201']; + expect(res.statusCode).toEqual(201); + expect(data).toHaveProperty('stockId'); + testValues.setValues('sugarId', data.stockId); + }); + + test('get stock list', async () => { + const res = await app.inject({ + method: 'GET', + url: '/api/stock', + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockListInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('stocks'); + expect(data.stocks).toHaveLength(7); + }); + + test('search stock', async () => { + const res = await app.inject({ + method: 'GET', + url: '/api/stock/search?name=자몽', + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockListInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('stocks'); + expect(data.stocks).toHaveLength(1); + }) + + test('get stock detail:grapefruit', async () => { + const res = await app.inject({ + method: 'GET', + url: `/api/stock/${testValues.grapefruitId}`, + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('name', '자몽'); + }); + + test('get stock detail:lemon', async () => { + const res = await app.inject({ + method: 'GET', + url: `/api/stock/${testValues.lemonId}`, + headers: testValues.storeHeader, + }); + const data = JSON.parse(res.body) as Stock.getStockInterface['Reply']['200']; + expect(res.statusCode).toEqual(200); + expect(data).toHaveProperty('name', '레몬'); + expect(data).toHaveProperty('unit', null); + }); +} diff --git a/test/integration/2. order/checkMenu.ts b/test/integration/2. order/checkMenu.ts new file mode 100644 index 0000000..65b6503 --- /dev/null +++ b/test/integration/2. order/checkMenu.ts @@ -0,0 +1,45 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Menu from '@DTO/menu.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('menu list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + const americano = body.categories[0].menus[0]; + const latte = body.categories[0].menus[1]; + const grapefruitAde = body.categories[1].menus[0]; + const lemonAde = body.categories[1].menus[1]; + expect(americano.id).toBe(testValues.americanoId); + expect(americano.stockStatus).toBe('OUT_OF_STOCK'); + expect(latte.id).toBe(testValues.latteId); + expect(latte.stockStatus).toBe('EMPTY'); + expect(grapefruitAde.id).toBe(testValues.grapefruitAdeId); + expect(grapefruitAde.stockStatus).toBe('UNKNOWN'); + expect(lemonAde.id).toBe(testValues.lemonAdeId); + expect(lemonAde.stockStatus).toBe('UNKNOWN'); + }); + + test('menu detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.lemonAdeId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuInterface['Reply']['200']; + expect(body.name).toBe('레몬에이드'); + expect(body.price).toBe("6000"); + expect(body.categoryId).toBe(testValues.adeCategoryId); + expect(body.recipe).toHaveLength(2); + expect(body.recipe[0].id).toBe(testValues.preservedLemonId); + expect(body.recipe[1].id).toBe(testValues.sparklingWaterId); + }); +} diff --git a/test/integration/2. order/checkMileage.ts b/test/integration/2. order/checkMileage.ts new file mode 100644 index 0000000..f39284a --- /dev/null +++ b/test/integration/2. order/checkMileage.ts @@ -0,0 +1,29 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Mileage from '@DTO/mileage.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('check mileage1 after order', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/mileage?phone=${testValues.customerPhone1}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Mileage.getMileageInterface['Reply']['200']; + expect(body.mileage).toBe(Math.floor((2500 * 68 + 6000)*0.05).toString()); + }); + + test('check mileage2 after order', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/mileage?phone=${testValues.customerPhone2}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Mileage.getMileageInterface['Reply']['200']; + expect(body.mileage).toBe(Math.floor(2500*4*0.05 + 6000*4*0.05).toString()); + }); +} diff --git a/test/integration/2. order/checkStock.ts b/test/integration/2. order/checkStock.ts new file mode 100644 index 0000000..6e73991 --- /dev/null +++ b/test/integration/2. order/checkStock.ts @@ -0,0 +1,46 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('check stock after order', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + const [water,coffeeBean, milk, grapefruit, lemon, sparklingWater, sugar] = body.stocks; + expect(water.id).toBe(testValues.waterId); + expect(coffeeBean.id).toBe(testValues.coffeeBeanId); + expect(milk.id).toBe(testValues.milkId); + expect(grapefruit.id).toBe(testValues.grapefruitId); + expect(lemon.id).toBe(testValues.lemonId); + expect(sparklingWater.id).toBe(testValues.sparklingWaterId); + expect(sugar.id).toBe(testValues.sugarId); + + expect(water.status).toBe('UNKNOWN'); + expect(water.usingMenuCount).toBe(1); + + expect(coffeeBean.status).toBe('OUT_OF_STOCK'); + expect(coffeeBean.usingMenuCount).toBe(2); + + expect(milk.status).toBe('EMPTY'); + expect(milk.usingMenuCount).toBe(1); + + expect(grapefruit.status).toBe('ENOUGH'); + expect(grapefruit.usingMenuCount).toBe(1); + + expect(lemon.status).toBe('UNKNOWN'); + expect(lemon.usingMenuCount).toBe(1); + + expect(sparklingWater.status).toBe('UNKNOWN'); + expect(sparklingWater.usingMenuCount).toBe(2); + + expect(sugar.status).toBe('ENOUGH'); + expect(sugar.usingMenuCount).toBe(2); + }); +} diff --git a/test/integration/2. order/index.ts b/test/integration/2. order/index.ts new file mode 100644 index 0000000..2bdf2f2 --- /dev/null +++ b/test/integration/2. order/index.ts @@ -0,0 +1,21 @@ +import { FastifyInstance } from 'fastify'; +import { afterAll, describe} from '@jest/globals'; +import preorder from './preorder'; +import order from './order'; +import checkStock from './checkStock'; +import checkMenu from './checkMenu'; +import checkMileage from './checkMileage'; + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['preorder', preorder], + ['order', order], + ['check stock after order', checkStock], + ['check menu after order', checkMenu], + ['check mileage after order', checkMileage], +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/2. order/order.ts b/test/integration/2. order/order.ts new file mode 100644 index 0000000..0d17366 --- /dev/null +++ b/test/integration/2. order/order.ts @@ -0,0 +1,251 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Order from '@DTO/order.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('order americano unitl stock becomes OUT_OF_STOCK', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order`, + headers: testValues.storeHeader, + payload: { + menus: [ + { + id: testValues.americanoId, + count: 68, + options: [1], + }, + { + id: testValues.grapefruitAdeId, + count: 1, + options: [1], + } + ], + totalPrice: (2500 * 68 + 6000).toString(), + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.newOrderInterface['Reply']['200']; + expect(body.orderId).toBeDefined(); + testValues.setValues('firstOrderId', body.orderId); + }); + + test('get order detail: before pay', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order/${testValues.firstOrderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderInterface['Reply']['200']; + expect(body.paymentStatus).toBe('WAITING'); + expect(body.pay).toBeUndefined(); + expect(body.mileage).toBeUndefined(); + expect(body.isPreOrdered).toBe(false); + }); + + test('pay first order: fail because of Not enough mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.firstOrderId, + paymentMethod: 'CARD', + mileageId: testValues.mileageId1, + useMileage: (2000).toString(), + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(403); + }); + + test('pay first order: fail because of unknown mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.firstOrderId, + paymentMethod: 'CARD', + mileageId: 999999, + useMileage: (1000).toString(), + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(404); + }); + + test('pay first order: fail because of unknown orderId', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: 999999, + paymentMethod: 'CARD', + mileageId: testValues.mileageId1, + useMileage: (1000).toString(), + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(404); + }); + + test('pay first order: fail because of ommited mileageInfo', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.firstOrderId, + paymentMethod: 'CARD', + mileageId: testValues.mileageId1, + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(400); + }); + + test('pay first order: success', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.firstOrderId, + paymentMethod: 'CARD', + mileageId: testValues.mileageId1, + useMileage: (1000).toString(), + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(200); + }); + + test('pay first order: fail because of already paid', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.firstOrderId, + paymentMethod: 'CARD', + mileageId: testValues.mileageId1, + useMileage: (1000).toString(), + saveMileage: Math.floor((2500 * 68 + 6000)*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(403); + }); + + test('order from preOrder', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order`, + headers: testValues.storeHeader, + payload: { + preOrderId: testValues.firstPreorderId, + totalPrice: (2500 * 4 + 6000 * 4).toString(), + menus: [ + { + id: testValues.latteId, + count: 4, + options: [1], + }, + { + id: testValues.lemonAdeId, + count: 4, + options: [1], + }, + ], + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.newOrderInterface['Reply']['200']; + expect(body.orderId).toBeDefined(); + testValues.setValues('secondOrderId', body.orderId); + }); + + test('get order list:before pay', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderListInterface['Reply']['200']; + expect(body.orders.length).toBe(2); + expect(body.orders[0].orderId).toBe(testValues.secondOrderId); + expect(body.orders[0].paymentMethod).toBeUndefined(); + expect(body.orders[1].orderId).toBe(testValues.firstOrderId); + }); + + test('pay second order: success', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: testValues.secondOrderId, + paymentMethod: 'CARD', + mileageId: testValues.mileageId2, + useMileage: "0", + saveMileage: Math.floor(2500*4*0.05 + 6000*4*0.05).toString(), + }, + }); + expect(response.statusCode).toBe(200); + }); + + test('get order list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderListInterface['Reply']['200']; + expect(body.orders.length).toBe(2); + expect(body.orders[0].orderId).toBe(testValues.secondOrderId); + expect(body.orders[1].orderId).toBe(testValues.firstOrderId); + }); + + test('get order list: with date', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order?date=${new Date().toISOString()}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderListInterface['Reply']['200']; + expect(body.orders.length).toBe(2); + expect(body.orders[0].orderId).toBe(testValues.secondOrderId); + expect(body.orders[1].orderId).toBe(testValues.firstOrderId); + }); + + test('get order detail:fail because of unknown orderId', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order/999999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('get order detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order/${testValues.secondOrderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderInterface['Reply']['200']; + expect(body.pay!.paymentMethod).toBe('CARD'); + expect(body.mileage).toBeDefined(); + expect(body.mileage!.use).toBe('0'); + expect(body.mileage!.save).toBe(Math.floor(2500*4*0.05 + 6000*4*0.05).toString()); + expect(body.isPreOrdered).toBe(true); + }); +} diff --git a/test/integration/2. order/preorder.ts b/test/integration/2. order/preorder.ts new file mode 100644 index 0000000..8e667de --- /dev/null +++ b/test/integration/2. order/preorder.ts @@ -0,0 +1,117 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Preorder from '@DTO/preOrder.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('preorder cafe latte and lemon ade', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/preorder`, + headers: testValues.storeHeader, + payload: { + menus: [ + { + id: testValues.latteId, + count: 4, + options: [1], + }, + { + id: testValues.lemonAdeId, + count: 4, + options: [1], + }, + ], + totalPrice: (2500 * 4 + 6000 * 4).toString(), + phone: '01011112222', + memo: '매장에서 먹을게요', + orderedFor: new Date().toISOString(), + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.newPreOrderInterface['Reply']['200']; + expect(body.preOrderId).toBeDefined(); + testValues.setValues('firstPreorderId', body.preOrderId); + }); + + test('preorder grapefurit ade', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/preorder`, + headers: testValues.storeHeader, + payload: { + menus: [ + { + id: testValues.grapefruitAdeId, + count: 10, + options: [1], + }, + ], + totalPrice: (6000 * 10).toString(), + phone: '01011112223', + orderedFor: new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString(), + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.newPreOrderInterface['Reply']['200']; + expect(body.preOrderId).toBeDefined(); + testValues.setValues('secondPreorderId', body.preOrderId); + }); + + test('get preorder list', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderListInterface['Reply']['200']; + expect(body.preOrders.length).toBe(1); + expect(body.preOrders[0].preOrderId).toBe(testValues.firstPreorderId); + }); + + test('get preorder list with date', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder?date=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString()}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderListInterface['Reply']['200']; + expect(body.preOrders.length).toBe(1); + expect(body.preOrders[0].preOrderId).toBe(testValues.secondPreorderId); + }); + + test('get preorder detail: fail because of unknown preorderId', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder/999999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('get preorder detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder/${testValues.firstPreorderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderInterface['Reply']['200']; + expect(body.totalCount).toBe(8); + }); + + test('get preorder detail', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder/${testValues.secondPreorderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderInterface['Reply']['200']; + expect(body.totalCount).toBe(10); + }); + +} diff --git a/test/integration/3. update/checkAfterUpdate.ts b/test/integration/3. update/checkAfterUpdate.ts new file mode 100644 index 0000000..7eb5937 --- /dev/null +++ b/test/integration/3. update/checkAfterUpdate.ts @@ -0,0 +1,153 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import * as Order from '@DTO/order.dto'; +import * as Preorder from '@DTO/preOrder.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('check stock after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + const [water,coffeeBean, milk, grapefruit, lemon, sparklingWater, sugar] = body.stocks; + expect(water.id).toBe(testValues.waterId); + expect(coffeeBean.id).toBe(testValues.coffeeBeanId); + expect(milk.id).toBe(testValues.milkId); + expect(grapefruit.id).toBe(testValues.grapefruitId); + expect(lemon.id).toBe(testValues.lemonId); + expect(sparklingWater.id).toBe(testValues.sparklingWaterId); + expect(sugar.id).toBe(testValues.sugarId); + + expect(water.status).toBe('UNKNOWN'); + expect(water.usingMenuCount).toBe(1); + + expect(coffeeBean.status).toBe('ENOUGH'); + expect(coffeeBean.usingMenuCount).toBe(2); + + expect(milk.status).toBe('EMPTY'); + expect(milk.usingMenuCount).toBe(2); + + expect(lemon.status).toBe('ENOUGH'); + expect(lemon.name).toBe('레몬즙'); + expect(lemon.usingMenuCount).toBe(1); + }); + + test('check mixed stock after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getMixedStockListInterface['Reply']['200']; + const [preservedGrapefruit, preservedLemon] = body.mixedStocks; + expect(preservedGrapefruit.id).toBe(testValues.preservedGrapefruitId); + expect(preservedLemon.id).toBe(testValues.preservedLemonId); + + expect(preservedLemon.name).toBe('레몬시럽'); + }); + + test('check menu after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + expect(body.categories[1].category).toBe('티&에이드'); + + const americano = body.categories[0].menus[0]; + const latte = body.categories[0].menus[1]; + const grapefruitAde = body.categories[1].menus[0]; + const lemonAde = body.categories[1].menus[1]; + + expect(americano.stockStatus).toBe('ENOUGH'); + expect(latte.stockStatus).toBe('EMPTY'); + expect(lemonAde.stockStatus).toBe('EMPTY'); + expect(grapefruitAde.stockStatus).toBe('ENOUGH'); + }); + + test('check option after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/option`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getOptionListInterface['Reply']['200']; + expect(body.option[2].options[1].id).not.toBe(testValues.shotMinusOptionId); + expect(body.option[2].options[1].name).toBe('샷 제외'); + expect(body.option[2].options[1].price).toBe("0"); + }); + + test('check menu detail after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.americanoId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuInterface['Reply']['200']; + expect(body.name).toBe('아메리카노(예시)'); + expect(body.price).toBe("2500"); + expect(body.categoryId).toBe(testValues.coffeeCategoryId); + expect(body.option[2].options[1].id).not.toBe(testValues.shotMinusOptionId); + expect(body.option[2].options[1].name).toBe('샷 제외'); + }); + + test('check order2 after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order/${testValues.secondOrderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderInterface['Reply']['200']; + const [latte, lemonAde] = body.orderitems; + expect(body.totalPrice).toBe((2500 * 4 + 6000 * 4).toString()); + expect(lemonAde.options).toEqual([{name: 'ice', price: '0'}]); + expect(lemonAde.count).toBe(4); + expect(lemonAde.menuName).toBe('레몬에이드'); + expect(lemonAde.price).toBe('6000'); + }); + + test('check preorder list after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder?date=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString()}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderListInterface['Reply']['200']; + expect(body.preOrders).toHaveLength(1); + const [preorder] = body.preOrders; + expect(preorder.preOrderId).toBe(testValues.secondPreorderId); + expect(preorder.totalPrice).toBe((6000 * 5 + 4500 * 5).toString()); + expect(preorder.phone).toBe('01011112223'); + expect(preorder.totalCount).toBe(10); + }); + + test('check preorder2 after update', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder/${testValues.secondPreorderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderInterface['Reply']['200']; + expect(body.totalPrice).toBe((6000 * 5 + 4500 * 5).toString()); + const [grapefruitAde, lemonAde] = body.orderitems; + expect(grapefruitAde.count).toBe(5); + expect(lemonAde.count).toBe(5); + expect(lemonAde.menuName).toBe('레몬에이드'); + expect(lemonAde.price).toBe('4500'); + }); +}; diff --git a/test/integration/3. update/index.ts b/test/integration/3. update/index.ts new file mode 100644 index 0000000..e09303c --- /dev/null +++ b/test/integration/3. update/index.ts @@ -0,0 +1,20 @@ +import { FastifyInstance } from 'fastify'; +import { afterAll, describe} from '@jest/globals'; +import updateStock from './updateStock'; +import updateMenu from './updateMenu'; +import updatePreOrder from './updatePreOrder'; +import checkAfterUpdate from './checkAfterUpdate'; + + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['update about stock', updateStock], + ['update about menu', updateMenu], + ['update preorder', updatePreOrder], + ['check after update about menu', checkAfterUpdate], +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/3. update/updateMenu.ts b/test/integration/3. update/updateMenu.ts new file mode 100644 index 0000000..833ba23 --- /dev/null +++ b/test/integration/3. update/updateMenu.ts @@ -0,0 +1,72 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Menu from '@DTO/menu.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('update lemonade menu', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/menu`, + headers: testValues.storeHeader, + payload: { + id: testValues.lemonAdeId, + name: '레몬에이드', + price: "4500", + categoryId: testValues.adeCategoryId, + option: [1,5], + recipe: [ + { + id: testValues.preservedLemonId, + isMixed: true, + unit: 'g', + coldRegularAmount: 50, + }, + { + id: testValues.sparklingWaterId, + isMixed: false, + unit: 'ml', + coldRegularAmount: 150, + }, + ], + } + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.updateMenuInterface['Reply']['201']; + expect(body.menuId).not.toBe(testValues.lemonAdeId); + testValues.setValues('lemonAdeId', body.menuId); + }); + + test('update ade category', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/menu/category`, + headers: testValues.storeHeader, + payload: { + id: testValues.adeCategoryId, + name: '티&에이드', + } + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.updateCategoryInterface['Reply']['201']; + expect(body.categoryId).toBe(testValues.adeCategoryId); + }); + + test('update option', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/menu/option`, + headers: testValues.storeHeader, + payload: { + optionId: testValues.shotMinusOptionId, + optionName: '샷 제외', + optionPrice: "0", + optionCategory: "샷" + } + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.updateOptionInterface['Reply']['201']; + expect(body.optionId).not.toBe(testValues.shotMinusOptionId); + }); +} diff --git a/test/integration/3. update/updatePreOrder.ts b/test/integration/3. update/updatePreOrder.ts new file mode 100644 index 0000000..010c301 --- /dev/null +++ b/test/integration/3. update/updatePreOrder.ts @@ -0,0 +1,38 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Preorder from '@DTO/preOrder.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('update second preorder', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/preorder`, + headers: testValues.storeHeader, + payload: { + id: testValues.secondPreorderId, + menus: [ + { + id: testValues.grapefruitAdeId, + count: 5, + options: [1], + }, + { + id: testValues.lemonAdeId, + count: 5, + options: [1], + } + ], + totalPrice: (6000 * 5 + 4500 * 5).toString(), + phone: '01011112223', + memo: '매장에서 먹을게요', + orderedFor: new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString(), + }, + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Preorder.updatePreOrderInterface['Reply']['201']; + expect(body.preOrderId).not.toBe(testValues.secondPreorderId); + testValues.setValues('secondPreorderId', body.preOrderId); + }); +} diff --git a/test/integration/3. update/updateStock.ts b/test/integration/3. update/updateStock.ts new file mode 100644 index 0000000..97e9c20 --- /dev/null +++ b/test/integration/3. update/updateStock.ts @@ -0,0 +1,101 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('update coffee bean stock', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/stock`, + headers: testValues.storeHeader, + payload: { + id: testValues.coffeeBeanId, + name: '케냐원두', + amount: 500, + price: "11900", + noticeThreshold: 1000, + unit: 'g', + currentAmount: 2000, + } as Stock.updateStockInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Stock.updateStockInterface['Reply']['201']; + expect(body.stockId).toBe(testValues.coffeeBeanId); + }); + + test('update lemon stock', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/stock`, + headers: testValues.storeHeader, + payload: { + id: testValues.lemonId, + name: '레몬즙', + amount: 1000, + price: "5000", + noticeThreshold: 500, + unit: null, + currentAmount: 1000, + } as Stock.updateStockInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Stock.updateStockInterface['Reply']['201']; + expect(body.stockId).toBe(testValues.lemonId); + }); + + test('update sparkling water stock', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/stock`, + headers: testValues.storeHeader, + payload: { + id: testValues.sparklingWaterId, + name: '탄산수', + amount: 100, + price: "171", + noticeThreshold: 500, + unit: null, + currentAmount: 1000, + } as Stock.updateStockInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Stock.updateStockInterface['Reply']['201']; + expect(body.stockId).toBe(testValues.sparklingWaterId); + }); + + test('update preserved lemon mixed stock', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/stock/mixed`, + headers: testValues.storeHeader, + payload: { + id: testValues.preservedLemonId, + name: '레몬시럽', + totalAmount: 1500, + unit: null, + mixing: [ + { + id: testValues.lemonId, + unit: 'ml', + amount: 400, + }, + { + id: testValues.sugarId, + unit: 'g', + amount: 1000, + }, + { + id: testValues.milkId, + unit: 'ml', + amount: 100, + } + ] + } as Stock.updateMixedStockInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Stock.updateMixedStockInterface['Reply']['201']; + expect(body.mixedStockId).toBe(testValues.preservedLemonId); + }); +} diff --git a/test/integration/4. delete/cancelOrder.ts b/test/integration/4. delete/cancelOrder.ts new file mode 100644 index 0000000..baf638a --- /dev/null +++ b/test/integration/4. delete/cancelOrder.ts @@ -0,0 +1,86 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import * as Order from '@DTO/order.dto'; +import * as Preorder from '@DTO/preOrder.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('cancel order: fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/order/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('cancel order: first order', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/order/${testValues.firstOrderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('check order after cancel', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderListInterface['Reply']['200']; + expect(body.orders).toHaveLength(2); + const [secondOrder, firstOrder] = body.orders; + expect(firstOrder.orderId).toBe(testValues.firstOrderId); + expect(firstOrder.paymentStatus).toBe('CANCELED'); + expect(firstOrder.isPreOrdered).toBe(false); + expect(secondOrder.orderId).toBe(testValues.secondOrderId); + expect(secondOrder.paymentStatus).toBe('PAID'); + expect(secondOrder.isPreOrdered).toBe(true); + }); + + test('cancel pre order: fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/preorder/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('check pre order before cancel', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder?date=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString()}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderListInterface['Reply']['200']; + expect(body.preOrders.length).toBe(1); + }); + + test('cancel pre order: second order', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/preorder/${testValues.secondPreorderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('check pre order after cancel', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/preorder?date=${new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString()}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Preorder.getPreOrderListInterface['Reply']['200']; + expect(body.preOrders.length).toBe(0); + }); +}; diff --git a/test/integration/4. delete/deleteMenu.ts b/test/integration/4. delete/deleteMenu.ts new file mode 100644 index 0000000..1d85014 --- /dev/null +++ b/test/integration/4. delete/deleteMenu.ts @@ -0,0 +1,201 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import * as Order from '@DTO/order.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('delete menu:fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('delete menu:lemon ade', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/${testValues.lemonAdeId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('check stock after delete menu', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + const [water,coffeeBean, grapefruit, lemon, sparklingWater, sugar] = body.stocks; + expect(water.id).toBe(testValues.waterId); + expect(coffeeBean.id).toBe(testValues.coffeeBeanId); + expect(grapefruit.id).toBe(testValues.grapefruitId); + expect(lemon.id).toBe(testValues.lemonId); + expect(sparklingWater.id).toBe(testValues.sparklingWaterId); + expect(sugar.id).toBe(testValues.sugarId); + + expect(water.status).toBe('UNKNOWN'); + expect(water.usingMenuCount).toBe(1); + + expect(coffeeBean.status).toBe('ENOUGH'); + expect(coffeeBean.usingMenuCount).toBe(2); + + expect(grapefruit.status).toBe('ENOUGH'); + expect(grapefruit.usingMenuCount).toBe(0); + + expect(lemon.status).toBe('ENOUGH'); + expect(lemon.name).toBe('레몬즙'); + expect(lemon.usingMenuCount).toBe(0); + + expect(sparklingWater.status).toBe('ENOUGH'); + expect(sparklingWater.usingMenuCount).toBe(1); + + expect(sugar.status).toBe('ENOUGH'); + expect(sugar.usingMenuCount).toBe(0); + }); + + test('check menu list after delete menu', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + expect(body.categories).toHaveLength(2); + expect(body.categories[1].menus).toHaveLength(1); + }); + + test('check order2 after delete menu', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/order/${testValues.secondOrderId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.getOrderInterface['Reply']['200']; + const [latte, lemonAde] = body.orderitems; + expect(body.totalPrice).toBe((2500 * 4 + 6000 * 4).toString()); + expect(lemonAde.options).toEqual([{name: 'ice', price: '0'}]); + expect(lemonAde.count).toBe(4); + expect(lemonAde.menuName).toBe('레몬에이드'); + expect(lemonAde.price).toBe('6000'); + }); + + test('delete category:fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/category/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('delete category:ade', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/category/${testValues.adeCategoryId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('delete option:fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/option/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('delete option:bean2', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/menu/option/${testValues.bean2OptionId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('check menu list after delete category', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + expect(body.categories).toHaveLength(1); + }); + + test('check stock after delete menu', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + const [water,coffeeBean, grapefruit, lemon, sparklingWater, sugar] = body.stocks; + expect(water.id).toBe(testValues.waterId); + expect(coffeeBean.id).toBe(testValues.coffeeBeanId); + expect(grapefruit.id).toBe(testValues.grapefruitId); + expect(lemon.id).toBe(testValues.lemonId); + expect(sparklingWater.id).toBe(testValues.sparklingWaterId); + expect(sugar.id).toBe(testValues.sugarId); + + expect(water.status).toBe('UNKNOWN'); + expect(water.usingMenuCount).toBe(1); + + expect(coffeeBean.status).toBe('ENOUGH'); + expect(coffeeBean.usingMenuCount).toBe(2); + + expect(grapefruit.status).toBe('ENOUGH'); + expect(grapefruit.usingMenuCount).toBe(0); + + expect(lemon.status).toBe('ENOUGH'); + expect(lemon.name).toBe('레몬즙'); + expect(lemon.usingMenuCount).toBe(0); + + expect(sparklingWater.status).toBe('ENOUGH'); + expect(sparklingWater.usingMenuCount).toBe(0); + + expect(sugar.status).toBe('ENOUGH'); + expect(sugar.usingMenuCount).toBe(0); + }); + + test('check option after delete option', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/option`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getOptionListInterface['Reply']['200']; + expect(body.option).toHaveLength(3); + expect(body.option[0].options).toHaveLength(2); + expect(body.option[1].options).toHaveLength(1); + expect(body.option[2].options).toHaveLength(2); + }); + + test('check menu detail after delete option', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.americanoId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuInterface['Reply']['200']; + expect(body.name).toBe('아메리카노(예시)'); + expect(body.option[1].options).toHaveLength(1); + expect(body.option[1].options[0].name).toBe('원두1'); + }); +}; diff --git a/test/integration/4. delete/deleteStock.ts b/test/integration/4. delete/deleteStock.ts new file mode 100644 index 0000000..e63e415 --- /dev/null +++ b/test/integration/4. delete/deleteStock.ts @@ -0,0 +1,173 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Stock from '@DTO/stock.dto'; +import * as Menu from '@DTO/menu.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + test('delete stock:fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/stock/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('delete stock', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/stock/${testValues.milkId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('delete mixed stock:fail', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/stock/mixed/99999`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(404); + }); + + test('delete mixed stock', async () => { + const response = await app.inject({ + method: 'DELETE', + url: `/api/stock/mixed/${testValues.preservedGrapefruitId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(204); + }); + + test('check stock after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getStockListInterface['Reply']['200']; + const [water,coffeeBean, grapefruit, lemon, sparklingWater, sugar] = body.stocks; + expect(water.id).toBe(testValues.waterId); + expect(coffeeBean.id).toBe(testValues.coffeeBeanId); + expect(grapefruit.id).toBe(testValues.grapefruitId); + expect(lemon.id).toBe(testValues.lemonId); + expect(sparklingWater.id).toBe(testValues.sparklingWaterId); + expect(sugar.id).toBe(testValues.sugarId); + + expect(water.status).toBe('UNKNOWN'); + expect(water.usingMenuCount).toBe(1); + + expect(coffeeBean.status).toBe('ENOUGH'); + expect(coffeeBean.usingMenuCount).toBe(2); + + expect(grapefruit.status).toBe('ENOUGH'); + expect(grapefruit.usingMenuCount).toBe(0); + + expect(lemon.status).toBe('ENOUGH'); + expect(lemon.name).toBe('레몬즙'); + expect(lemon.usingMenuCount).toBe(1); + + expect(sparklingWater.status).toBe('ENOUGH'); + expect(sparklingWater.usingMenuCount).toBe(2); + + expect(sugar.status).toBe('ENOUGH'); + expect(sugar.usingMenuCount).toBe(1); + }); + + test('check mixed stock after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getMixedStockListInterface['Reply']['200']; + expect(body.mixedStocks).toHaveLength(1); + expect(body.mixedStocks[0].id).toBe(testValues.preservedLemonId); + }); + + test('check mixed stock detail after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/mixed/${testValues.preservedLemonId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.getMixedStockInterface['Reply']['200']; + expect(body.mixing).toHaveLength(2); + expect(body.mixing[0].id).toBe(testValues.lemonId); + expect(body.mixing[1].id).toBe(testValues.sugarId); + }); + + test('check menu after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuListInterface['Reply']['200']; + expect(body.categories[1].category).toBe('티&에이드'); + const latte = body.categories[0].menus[1]; + const grapefruitAde = body.categories[1].menus[0]; + const lemonAde = body.categories[1].menus[1]; + expect(latte.stockStatus).toBe('ENOUGH'); + expect(latte.name).toBe('카페라떼(예시)'); + expect(grapefruitAde.stockStatus).toBe('ENOUGH'); + expect(grapefruitAde.name).toBe('자몽에이드'); + expect(lemonAde.stockStatus).toBe('ENOUGH'); + expect(lemonAde.name).toBe('레몬에이드'); + }); + + test('check menu detail after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.latteId}`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Menu.getMenuInterface['Reply']['200']; + expect(body.recipe).toHaveLength(1); + expect(body.recipe[0].id).toBe(testValues.coffeeBeanId); + + const response2 = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.lemonAdeId}`, + headers: testValues.storeHeader, + }); + expect(response2.statusCode).toBe(200); + const body2 = JSON.parse(response2.body) as Menu.getMenuInterface['Reply']['200']; + expect(body2.price).toBe("4500"); + expect(body2.category).toBe('티&에이드'); + expect(body2.recipe).toHaveLength(2); + expect(body2.recipe[0].id).toBe(testValues.preservedLemonId); + expect(body2.recipe[1].id).toBe(testValues.sparklingWaterId); + + const response3 = await app.inject({ + method: 'GET', + url: `/api/menu/${testValues.grapefruitAdeId}`, + headers: testValues.storeHeader, + }); + expect(response3.statusCode).toBe(200); + const body3 = JSON.parse(response3.body) as Menu.getMenuInterface['Reply']['200']; + expect(body3.price).toBe("6000"); + expect(body3.category).toBe('티&에이드'); + expect(body3.recipe).toHaveLength(1); + expect(body3.recipe[0].id).toBe(testValues.sparklingWaterId); + }); + + test('check search stock after delete', async () => { + const response = await app.inject({ + method: 'GET', + url: `/api/stock/search?name=우유`, + headers: testValues.storeHeader, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Stock.searchStockInterface['Reply']['200']; + expect(body.stocks).toHaveLength(0); + }); +}; diff --git a/test/integration/4. delete/index.ts b/test/integration/4. delete/index.ts new file mode 100644 index 0000000..28f1650 --- /dev/null +++ b/test/integration/4. delete/index.ts @@ -0,0 +1,17 @@ +import { FastifyInstance } from 'fastify'; +import { afterAll, describe} from '@jest/globals'; +import deleteStock from './deleteStock'; +import deleteMenu from './deleteMenu'; +import cancelOrder from './cancelOrder'; + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['delete stock', deleteStock], + ['delete menu', deleteMenu], + ['cancle order', cancelOrder], +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/400test.ts b/test/integration/400test.ts deleted file mode 100644 index 3c0e02e..0000000 --- a/test/integration/400test.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { test, expect } from '@jest/globals'; -import { FastifyInstance } from 'fastify'; - -export default async (app:FastifyInstance,apis:[string,'POST'|'GET'|'PUT'|'PATCH'][]) => { - const responses = await Promise.all(apis.map(async (api) => { - const response = await app.inject({ - method: api[1], - url: api[0], - payload: {} - }); - return response; - })); - responses.forEach((response) => { - expect(response.statusCode).toBe(400); - }); -} diff --git a/test/integration/5. etc./createWithoutParams.ts b/test/integration/5. etc./createWithoutParams.ts new file mode 100644 index 0000000..bbdae57 --- /dev/null +++ b/test/integration/5. etc./createWithoutParams.ts @@ -0,0 +1,108 @@ +import { FastifyInstance } from 'fastify'; +import { ErrorInterface } from '@DTO/index.dto'; +import testValues from '../testValues'; +import * as Menu from '@DTO/menu.dto'; +import * as Order from '@DTO/order.dto'; +import { expect, test } from '@jest/globals'; + +export default (app: FastifyInstance) => () => { + let tempOrderId: number; + test('order without mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order`, + headers: testValues.storeHeader, + payload: { + menus: [ + { + id: testValues.americanoId, + count: 1, + options: [1], + }, + ], + totalPrice: (2500).toString(), + }, + }); + expect(response.statusCode).toBe(200); + const body = JSON.parse(response.body) as Order.newOrderInterface['Reply']['200']; + expect(body.orderId).toBeDefined(); + tempOrderId = body.orderId; + }); + + test('pay without mileage', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/order/pay`, + headers: testValues.storeHeader, + payload: { + orderId: tempOrderId, + paymentMethod: 'CARD', + } as Order.payInterface['Body'], + }); + expect(response.statusCode).toBe(200); + }); + + let tempMenuId: number; + test('register menu without option, recipe', async () => { + const response = await app.inject({ + method: 'POST', + url: `/api/menu`, + headers: testValues.storeHeader, + payload: { + name: 'new menu', + price: "1000", + categoryId: testValues.coffeeCategoryId, + } as Menu.createMenuInterface['Body'], + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.createMenuInterface['Reply']['201']; + expect(body.menuId).toBeDefined(); + tempMenuId = body.menuId; + }); + + test('update menu without option, recipe', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/menu`, + headers: testValues.storeHeader, + payload: { + id: tempMenuId, + name: 'new menu', + price: "1000", + categoryId: testValues.coffeeCategoryId, + } as Menu.updateMenuInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.updateMenuInterface['Reply']['201']; + expect(body.menuId).not.toBe(tempMenuId); + }); + + test('update menu with recipe with unit', async () => { + const response = await app.inject({ + method: 'PUT', + url: `/api/menu`, + headers: testValues.storeHeader, + payload: { + id: tempMenuId, + name: 'new menu', + price: "1000", + categoryId: testValues.coffeeCategoryId, + recipe: [ + { + id: testValues.coffeeBeanId, + unit: 'g', + isMixed: false, + }, + { + id: testValues.preservedLemonId, + unit: 'g', + isMixed: true, + } + ], + } as Menu.updateMenuInterface['Body'] + }); + expect(response.statusCode).toBe(201); + const body = JSON.parse(response.body) as Menu.updateMenuInterface['Reply']['201']; + expect(body.menuId).not.toBe(tempMenuId); + }); +}; diff --git a/test/integration/5. etc./index.ts b/test/integration/5. etc./index.ts new file mode 100644 index 0000000..e514586 --- /dev/null +++ b/test/integration/5. etc./index.ts @@ -0,0 +1,13 @@ +import { FastifyInstance } from 'fastify'; +import { afterAll, describe} from '@jest/globals'; +import createWithoutParams from './createWithoutParams'; + +const tests:[string, (app: FastifyInstance) => () => void][] = [ + ['create without params', createWithoutParams], +]; + +export default (app: FastifyInstance) => () => { + tests.forEach(([name, test]) => { + describe(name, test(app)); + }); +}; diff --git a/test/integration/hookTest.test.ts b/test/integration/hookTest.test.ts deleted file mode 100644 index 336cb69..0000000 --- a/test/integration/hookTest.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import server from '@server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; -import { ErrorInterface } from '@DTO/index.dto'; -import { LoginToken } from '@utils/jwt'; -import seedValues from './seedValues'; - -let app: FastifyInstance; - -beforeAll(async () => { - app = await server(); -}); - -afterAll(async () => { - await app.close(); -}); - -test('ping', async () => { - const response = await app.inject({ - method: 'GET', - url: '/api/ping', - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse(response.body) as { data: string }; - expect(body.data).toBe('pong'); -}); - -test('human error', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/notDefinedOnConfigerror', - }); - expect(response.statusCode).toBe(500); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('notDefinedOnConfigerror'); -}); - -test('notDefinederror', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/notDefinederror', - }); - expect(response.statusCode).toBe(500); -}); - -const accessToken = new LoginToken(seedValues.user.id).signAccessToken(); -const customerPhone = '01043218765'; - -test('register mileage without storeid header', async () => { - // api 폴더 hooks 폴더 checkStoreIdUser.ts 에서 에러 체크 - // storeid가 없는 경우 에러를 던지도록 설정 - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - }, - payload: { - phone: customerPhone, - }, - }); - expect(response.statusCode).toBe(400); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('헤더에 storeid가 없습니다'); -}); - -test('register mileage without storeid header', async () => { - // api 폴더 hooks 폴더 checkStoreIdUser.ts 에서 에러 체크 - // 잘못된 토큰 확인 - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}1`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: customerPhone, - }, - }); - expect(response.statusCode).toBe(401); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('유저 인증에 실패했습니다'); -}); - -test('register mileage without storeid header', async () => { - // api 폴더 hooks 폴더 checkStoreIdUser.ts 에서 에러 체크 - // 잘못된 스토어 아이디 확인 - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: '123242214', - }, - payload: { - phone: customerPhone, - }, - }); - expect(response.statusCode).toBe(401); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('가게 인증에 실패했습니다'); -}); diff --git a/test/integration/index.test.ts b/test/integration/index.test.ts new file mode 100644 index 0000000..ee46a0f --- /dev/null +++ b/test/integration/index.test.ts @@ -0,0 +1,22 @@ +import server from '@server'; +import { FastifyInstance } from 'fastify'; +import { afterAll,describe } from '@jest/globals'; +import init from './0. init'; +import register from './1. register'; +import order from './2. order'; +import update from './3. update'; +import del from './4. delete'; +import etc from './5. etc.'; + +const app: FastifyInstance = await server(); + +afterAll(async () => { + await app.close(); +}); + +describe('init',init(app)); +describe('register',register(app)); +describe('order',order(app)); +describe('update',update(app)); +describe('delete',del(app)); +describe('etc',etc(app)); diff --git a/test/integration/menuService.test.ts b/test/integration/menuService.test.ts deleted file mode 100644 index 09f10aa..0000000 --- a/test/integration/menuService.test.ts +++ /dev/null @@ -1,1118 +0,0 @@ -import server from '@server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; -import { LoginToken } from '@utils/jwt'; -import seedValues from './seedValues'; -import * as Menu from '@DTO/menu.dto'; -import * as Stock from '@DTO/stock.dto'; -import * as Order from '@DTO/order.dto'; -import { ErrorInterface } from '@DTO/index.dto'; - -let app: FastifyInstance; - -const accessToken = new LoginToken(seedValues.user.id).signAccessToken(); -beforeAll(async () => { - app = await server(); -}); - -afterAll(async () => { - await app.close(); -}); -let categoryId: number; -test('new menu category', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/menu/category`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '디저트', - sort: 7, - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.createCategoryInterface['Reply']['201']; - categoryId = body.categoryId; - expect(body).toEqual({ - categoryId: 7, - }); -}); - -test('update menu category', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/menu/category`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - id: categoryId, - name: '테스트 변경', - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.updateCategoryInterface['Reply']['201']; - expect(body).toEqual({ - categoryId: 7, - }); -}); - -test('soft delete category', async () => { - const response = await app.inject({ - method: 'DELETE', - url: `/api/menu/category/${categoryId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Menu.softDeleteCategoryInterface['Reply']['200']; - expect(body).toEqual({ - categoryId: 7, - }); -}); - -let mintChoco: number; -test('create stock with name only', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/stock`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민트초코 시럽', - }, - }); - - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Stock.createStockInterface['Reply']['201']; - mintChoco = body.stockId; -}); - -let cock: number; -test('create stock with name and price', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/stock`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '콜라', - amount: 8520, - unit: 'ml', - price: 23900, - }, - }); - - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Stock.createStockInterface['Reply']['201']; - cock = body.stockId; -}); - -test('create stock and soft delete', async () => { - const createResponse = await app.inject({ - method: 'POST', - url: `/api/stock`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '콜라', - amount: 8520, - unit: 'ml', - price: 23900, - }, - }); - - expect(createResponse.statusCode).toBe(201); - const body = JSON.parse( - createResponse.body - ) as Stock.createStockInterface['Reply']['201']; - - const softDeleteResponse = await app.inject({ - method: 'DELETE', - url: `/api/stock/${body.stockId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - - expect(softDeleteResponse.statusCode).toBe(200); - const body2 = JSON.parse( - softDeleteResponse.body - ) as Stock.softDeleteStockInterface['Reply']['200']; - expect(body2).toEqual({ - stockId: body.stockId, - }); -}); - -test('new menu', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민초콕', - price: 3000, - categoryId: 2, - option: [1, 3, 4], - recipe: [ - { - id: mintChoco, - isMixed: false, - unit: 'ml', - coldRegularAmount: 50, - }, - { - id: cock, - isMixed: false, - unit: 'ml', - coldRegularAmount: 150, - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.createMenuInterface['Reply']['201']; - expect(body).toEqual({ - menuId: 45, - }); -}); - -test('new menu and soft delete', async () => { - const createResponse = await app.inject({ - method: 'POST', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민초콕', - price: 3000, - categoryId: 2, - option: [1, 3, 4], - recipe: [ - { - id: mintChoco, - isMixed: false, - unit: 'ml', - coldRegularAmount: 50, - }, - { - id: cock, - isMixed: false, - unit: 'ml', - coldRegularAmount: 150, - }, - ], - }, - }); - expect(createResponse.statusCode).toBe(201); - const body = JSON.parse( - createResponse.body - ) as Menu.createMenuInterface['Reply']['201']; - const softDeleteResponse = await app.inject({ - method: 'DELETE', - url: `/api/menu/${body.menuId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(softDeleteResponse.statusCode).toBe(200); - const body2 = JSON.parse( - createResponse.body - ) as Menu.softDeleteMenuInterface['Reply']['200']; - expect(body2).toEqual({ - menuId: body.menuId, - }); -}); - -let sugar: number; -test('search stock', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/search?name=설탕`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.searchStockInterface['Reply']['200']; - sugar = body.stocks[0].id; -}); - -let chocoSyrup: number; -test('search stock', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/search?name=초코시럽`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.searchStockInterface['Reply']['200']; - chocoSyrup = body.stocks[0].id; -}); - -let mintChocoChung: number; -test('new mixedStock', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/stock/mixed`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민트초코 청', - mixing: [ - { - id: mintChoco, - unit: 'ml', - amount: 1000, - }, - { - id: sugar, - unit: 'g', - amount: 1000, - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Stock.createMixedStockInterface['Reply']['201']; - mintChocoChung = body.mixedStockId; -}); - -test('new mixedStock and delete', async () => { - const createResponse = await app.inject({ - method: 'POST', - url: `/api/stock/mixed`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민트초코 청', - mixing: [ - { - id: mintChoco, - unit: 'ml', - amount: 1000, - }, - { - id: sugar, - unit: 'g', - amount: 1000, - }, - ], - }, - }); - expect(createResponse.statusCode).toBe(201); - const body = JSON.parse( - createResponse.body - ) as Stock.createMixedStockInterface['Reply']['201']; - const softDeleteResponse = await app.inject({ - method: 'DELETE', - url: `/api/stock/mixed/${body.mixedStockId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(softDeleteResponse.statusCode).toBe(200); - const body2 = JSON.parse( - softDeleteResponse.body - ) as Stock.softDeleteMixedStockInterface['Reply']['200']; - expect(body2).toEqual({ - mixedStockId: body.mixedStockId, - }); -}); - -test('get stock detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/${mintChoco}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getStockInterface['Reply']['200']; - expect(body).toEqual({ - name: '민트초코 시럽', - price: '0', - amount: null, - currentAmount: null, - noticeThreshold: 0, - unit: 'ml', - updatedAt: expect.any(String), - }); -}); - -test('update stock', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/stock`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - id: mintChoco, - name: '민트초코 시럽', - price: 3000, - amount: 1000, - unit: 'ml', - currentAmount: 1000, - noticeThreshold: 500, - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Stock.updateStockInterface['Reply']['201']; - expect(body).toEqual({ - stockId: mintChoco, - }); -}); - -test('get stock detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/${mintChoco}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getStockInterface['Reply']['200']; - expect(body).toEqual({ - name: '민트초코 시럽', - price: '3000', - amount: 1000, - currentAmount: 1000, - noticeThreshold: 500, - unit: 'ml', - updatedAt: expect.any(String), - }); -}); - -test('get stock detail fail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/456784353456`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); -}); - -test('get mixed stock list', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/mixed`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getMixedStockListInterface['Reply']['200']; - const mintChocoChung = body.mixedStocks.find( - (stock) => stock.name === '민트초코 청' - ); - expect(mintChocoChung).toEqual({ - id: expect.any(Number), - name: '민트초코 청', - }); -}); - -test('update mixed stock', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/stock/mixed`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - id: mintChocoChung, - name: '민트초코 청', - totalAmount: 2200, - unit: 'g', - mixing: [ - { - id: mintChoco, - unit: 'ml', - amount: 1000, - }, - { - id: sugar, - unit: 'g', - amount: 1000, - }, - { - id: chocoSyrup, - unit: 'ml', - amount: 200, - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Stock.updateMixedStockInterface['Reply']['201']; - expect(body).toEqual({ - mixedStockId: mintChocoChung, - }); -}); - -test('get mixed stock detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/mixed/${mintChocoChung}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getMixedStockInterface['Reply']['200']; - expect(body).toEqual({ - name: '민트초코 청', - totalAmount: 2200, - unit: 'g', - mixing: [ - { - id: mintChoco, - name: '민트초코 시럽', - amount: 1000, - unit: 'ml', - }, - { - id: sugar, - name: '설탕', - amount: 1000, - unit: 'g', - }, - { - id: chocoSyrup, - name: '초코시럽', - amount: 200, - unit: 'ml', - }, - ], - }); -}); - -test('get mixed stock detail fail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/mixed/456784353456`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); -}); - -let sparklingWater: number; -test('search stock', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/withMixed/search?name=탄산수`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.searchStockAndMixedStockInterface['Reply']['200']; - sparklingWater = body.stocks[0].id; -}); - -test('search stock and mixed stock', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/withMixed/search?name=민트초코`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.searchStockAndMixedStockInterface['Reply']['200']; - expect(body.stocks.length).toBeGreaterThanOrEqual(2); -}); - -test('new menu without option', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '민트초코 에이드', - price: 3000, - categoryId: 2, - recipe: [ - { - id: sparklingWater, - isMixed: false, - unit: 'ml', - coldRegularAmount: 150, - }, - { - id: mintChocoChung, - isMixed: true, - unit: 'ml', - coldRegularAmount: 50, - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.createMenuInterface['Reply']['201']; - expect(body).toEqual({ - menuId: 47, - }); -}); - -test('get stock list', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getStockListInterface['Reply']['200']; - const mintChoco = body.stocks.find((stock) => stock.name === '민트초코 시럽'); - expect(mintChoco).toEqual({ - id: expect.any(Number), - name: '민트초코 시럽', - status: 'ENOUGH', - usingMenuCount: 2, - }); - const cock = body.stocks.find((stock) => stock.name === '콜라'); - expect(cock).toEqual({ - id: expect.any(Number), - name: '콜라', - status: 'UNKNOWN', - usingMenuCount: 1, - }); -}); - -test('get menu list', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Menu.getMenuListInterface['Reply']['200']; - expect(body.categories[0]).toEqual({ - category: '커피', - categoryId: 1, - menus: expect.any(Array) - }); - expect(body.categories[1]).toEqual({ - category: '티&에이드', - categoryId: 2, - menus: [ - { - id: 3, - name: '아이스티', - price: '2500', - stockStatus: expect.any(String) - }, - { - id: 45, - name: '민초콕', - price: '3000', - stockStatus: expect.any(String) - }, - { - id: 47, - name: '민트초코 에이드', - price: '3000', - stockStatus: expect.any(String) - }, - ], - }); -}); - -test('get menu detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/menu/45`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Menu.getMenuInterface['Reply']['200']; - expect(body).toEqual({ - name: '민초콕', - price: '3000', - categoryId: 2, - category: '티&에이드', - option: [ - { - optionType: '온도', - options: [ - { - id: 1, - name: 'ice', - price: '0', - isSelectable: true, - }, - { - id: 2, - name: 'hot', - price: '0', - isSelectable: false, - }, - ], - }, - { - optionType: '원두', - options: [ - { - id: 3, - name: '케냐', - price: '0', - isSelectable: true, - }, - { - id: 4, - name: '콜롬비아', - price: '300', - isSelectable: true, - }, - ], - }, - { - optionType: '샷', - options: [ - { - id: 5, - name: '1샷 추가', - price: '500', - isSelectable: false, - }, - { - id: 6, - name: '연하게', - price: '0', - isSelectable: false, - }, - ], - }, - ], - recipe: [ - { - id: mintChoco, - name: '민트초코 시럽', - coldRegularAmount: 50, - coldSizeUpAmount: null, - hotRegularAmount: null, - hotSizeUpAmount: null, - isMixed: false, - unit: 'ml', - }, - { - id: cock, - name: '콜라', - coldRegularAmount: 150, - coldSizeUpAmount: null, - hotRegularAmount: null, - hotSizeUpAmount: null, - isMixed: false, - unit: 'ml', - }, - ], - }); -}); -test('get not exist menu detail', async () => { - // service 폴더 내 menuService.test.ts 에서 에러 체크 - // getMenu에서 menu가 존재하지 않는 경우 에러를 던지도록 설정 - const response = await app.inject({ - method: 'GET', - url: `/api/menu/100`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('메뉴가 존재하지 않습니다.'); -}); - -test('update menu', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - id: 3, - name: '아이스티', - price: 2500, - categoryId: 2, - option: [1, 3, 5, 6], - recipe: [ - { - id: sparklingWater, - isMixed: false, - unit: 'ml', - coldRegularAmount: 150, - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.updateMenuInterface['Reply']['201']; - expect(body).toEqual({ - menuId: 48, - }); -}); - -test('create menu without option and recipe', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - name: '오렌지에이드', - price: 2500, - categoryId: 2, - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.updateMenuInterface['Reply']['201']; - expect(body).toEqual({ - menuId: 49, - }); -}); - -test('update menu without option and recipe', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/menu`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - body: { - id: 49, - name: '오렌지에이드', - price: 2500, - categoryId: 2, - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as Menu.updateMenuInterface['Reply']['201']; - expect(body).toEqual({ - menuId: 50, - }); -}); - -test('get menu detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/menu/48`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Menu.getMenuInterface['Reply']['200']; - expect(body).toEqual({ - name: '아이스티', - price: '2500', - categoryId: 2, - category: '티&에이드', - option: [ - { - optionType: '온도', - options: [ - { - id: 1, - name: 'ice', - price: '0', - isSelectable: true, - }, - { - id: 2, - name: 'hot', - price: '0', - isSelectable: false, - }, - ], - }, - { - optionType: '원두', - options: [ - { - id: 3, - name: '케냐', - price: '0', - isSelectable: true, - }, - { - id: 4, - name: '콜롬비아', - price: '300', - isSelectable: false, - }, - ], - }, - { - optionType: '샷', - options: [ - { - id: 5, - name: '1샷 추가', - price: '500', - isSelectable: true, - }, - { - id: 6, - name: '연하게', - price: '0', - isSelectable: true, - }, - ], - }, - ], - recipe: expect.any(Array), - }); -}); - -let orderId: number; -test('order', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - totalPrice: '3000', - menus: [ - { - id: 45, - count: 1, - options: [], - }, - ], - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - orderId = body.orderId; - expect(body.orderId).toBeDefined(); -}); - -test('pay', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - }, - }); - - expect(response.statusCode).toBe(200); -}); - -test('get stock detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/${mintChoco}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getStockInterface['Reply']['200']; - expect(body).toEqual({ - name: '민트초코 시럽', - price: '3000', - amount: 1000, - currentAmount: 950, - noticeThreshold: 500, - unit: 'ml', - updatedAt: expect.any(String), - }); -}); - -let mixedOrderId: number; -test('order', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - totalPrice: '3000', - menus: [ - { - id: 47, - count: 2, - options: [], - }, - ], - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - mixedOrderId = body.orderId; - expect(body.orderId).toBeDefined(); -}); - -test('pay', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: mixedOrderId, - paymentMethod: 'CARD', - }, - }); - - expect(response.statusCode).toBe(200); -}); - -test('get stock detail', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/stock/${mintChoco}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Stock.getStockInterface['Reply']['200']; - expect(body).toEqual({ - name: '민트초코 시럽', - price: '3000', - amount: 1000, - currentAmount: 904, - noticeThreshold: 500, - unit: 'ml', - updatedAt: expect.any(String), - }); -}); diff --git a/test/integration/orderService.test.ts b/test/integration/orderService.test.ts deleted file mode 100644 index 6d55fca..0000000 --- a/test/integration/orderService.test.ts +++ /dev/null @@ -1,579 +0,0 @@ -import server from '@server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, expect, test } from '@jest/globals'; -import { LoginToken } from '@utils/jwt'; -import * as Order from '@DTO/order.dto'; -import * as Mileage from '@DTO/mileage.dto'; -import { Prisma } from '@prisma/client'; -import test400 from './400test'; -import { ErrorInterface } from '@DTO/index.dto'; -import seedValues from './seedValues'; -let app: FastifyInstance; - -const accessToken = new LoginToken(seedValues.user.id).signAccessToken(); -beforeAll(async () => { - app = await server(); -}); - -afterAll(async () => { - await app.close(); -}); - -test('400 test', async () => { - await test400(app, [ - ['/api/order', 'POST'], - ['/api/order', 'GET'], - ['/api/order/1', 'GET'], - ['/api/order/pay', 'POST'], - ['/api/mileage', 'POST'], - ['/api/mileage', 'GET'], - ['/api/mileage', 'PATCH'], - ]); -}); - -const customerPhone = '01043218765'; -const existPhone = '01023456789'; -const notCorrectPhone = '010-1234-567'; -const current = () => new Date().toISOString(); -let mileageId: number; -test('mileage', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/mileage?phone=${customerPhone}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('해당하는 마일리지가 없습니다.'); - expect(body.toast).toBe('마일리지을(를) 찾을 수 없습니다.'); -}); - -test('register mileage', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: customerPhone, - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Mileage.registerMileageInterface['Reply']['200']; - mileageId = body.mileageId; - expect(body.mileageId).toBeDefined(); -}); - -test('register mileage with not correct phone check', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: notCorrectPhone, - }, - }); - expect(response.statusCode).toBe(400); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('입력된 전화번호이(가) 양식과 맞지 않습니다.'); -}); - -test('exist mileage check', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: existPhone, - }, - }); - expect(response.statusCode).toBe(409); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('입력된 전화번호가 이미 존재합니다.'); - expect(body.toast).toBe('입력된 전화번호가 이미 존재합니다.'); -}); - -test('add mileage', async () => { - const response = await app.inject({ - method: 'PATCH', - url: `/api/mileage`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - mileageId: mileageId, - mileage: 1000, - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Mileage.saveMileageInterface['Reply']['200']; - expect(body.mileage).toBe('1000'); -}); - -test('get mileage', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/mileage?phone=${customerPhone}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Mileage.getMileageInterface['Reply']['200']; - expect(body.mileage).toBe('1000'); - expect(body.mileageId).toBe(mileageId); -}); - -let orderId: number; -test('order', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '얼음 따로 포장해주세요', - }, - ], - }, - }); - - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - orderId = body.orderId; - expect(body.orderId).toBeDefined(); -}); - -let orderId2: number; -test('order 2', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - payload: { - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '얼음 따로 포장해주세요', - }, - ], - }, - }); - - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - orderId2 = body.orderId; - expect(body.orderId).toBeDefined(); -}); - -test('order 3', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '얼음 따로 포장해주세요', - }, - ], - }, - }); - - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - expect(body.orderId).toBeDefined(); -}); - -test('not pay cause not enough mileage', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - mileageId: mileageId, - useMileage: 10000, - saveMileage: Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - 0.1 - ), - }, - }); - expect(response.statusCode).toBe(403); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('마일리지가 부족합니다.'); -}); - -test('pay with not exist mileage', async () => { - // orderService.test 에서 pay에서 마일리지가 없는 경우 에러 발생 - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - mileageId: 500, - useMileage: 500, - saveMileage: Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ), - 0.1 - ), - }, - }); - - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('해당하는 마일리지가 없습니다.'); -}); - -test('pay without useMileage and saveMileage', async () => { - // orderService.test 에서 pay에서 사용할 마일리지와 저장할 마일리지 값이 없는 경우 에러 발생 - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - mileageId: mileageId, - }, - }); - - expect(response.statusCode).toBe(400); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe( - '사용할 마일리지와 적립할 마일리지를 입력해주세요.' - ); -}); - -test('pay', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - mileageId: mileageId, - useMileage: 500, - saveMileage: Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ), - 0.1 - ), - }, - }); - - expect(response.statusCode).toBe(200); -}); - -test('pay again', async () => { - // orderService.test 에서 pay에서 orderId paymentStatus가 WAITING이 아닐 때 에러 발생 - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: orderId, - paymentMethod: 'CARD', - mileageId: mileageId, - useMileage: 500, - saveMileage: Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ), - 0.1 - ), - }, - }); - - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('이미 결제된 주문입니다.'); -}); - -test('pay not exist order', async () => { - // orderService.test 에서 pay에서 없는 order를 결제할 때 에러 발생 - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - orderId: 100, - paymentMethod: 'CARD', - mileageId: mileageId, - useMileage: 500, - saveMileage: Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ), - 0.1 - ), - }, - }); - - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toBe('해당하는 주문이 없습니다.'); -}); - -test('pay with not use mileage and save mileage', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/order/pay`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - payload: { - orderId: orderId2, - paymentMethod: 'CARD', - }, - }); - expect(response.statusCode).toBe(200); -}); - -//TODO: milage 없는 경우, 이미 결제된 주문을 다시 결제하는 경우 테스트 필요 - -test('get order', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/order/${orderId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.getOrderInterface['Reply']['200']; - expect(body.paymentStatus).toEqual('PAID'); - expect(body.pay.paymentMethod).toEqual('CARD'); - expect(body.pay.price).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ).toString() - ); - expect(body.totalPrice).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ).toString() - ); - expect(body.mileage).toBeDefined(); - if (body.mileage === undefined) return; - expect(body.mileage.mileageId).toEqual(mileageId); - expect(body.mileage.use).toEqual('500'); - expect(body.mileage.save).toEqual( - Prisma.Decimal.mul( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price, - -500 - ), - 0.1 - ).toString() - ); -}); - -test('get order without mileage', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/order/${orderId2}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.getOrderInterface['Reply']['200']; - expect(body.paymentStatus).toEqual('PAID'); - expect(body.pay.paymentMethod).toEqual('CARD'); - expect(body.pay.price).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ).toString() - ); - expect(body.totalPrice).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ).toString() - ); - expect(body.mileage).toEqual(undefined); -}); - -test('get not exist order', async () => { - // orderService.test 에서 getOrder에서 order가 없을 때 에러 발생 - const response = await app.inject({ - method: 'GET', - url: `/api/order/${100}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toEqual('해당하는 주문이 없습니다.'); -}); - -test('get order but wrong storeId', async () => { - // orderService.test 에서 getOrder에서 order가 없을 때 에러 발생 - const response = await app.inject({ - method: 'GET', - url: `/api/order/${orderId2}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toEqual('해당하는 주문이 없습니다.'); -}); - -test('get order list', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/order?page=1&count=10&date=${current()}`, - - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.getOrderListInterface['Reply']['200']; - expect(body.orders.length).toBeGreaterThan(0); - const order = body.orders[1]; - expect(order.orderId).toEqual(orderId); - expect(order.totalCount).toEqual(3); -}); diff --git a/test/integration/preOrderService.test.ts b/test/integration/preOrderService.test.ts deleted file mode 100644 index 98d7f0f..0000000 --- a/test/integration/preOrderService.test.ts +++ /dev/null @@ -1,381 +0,0 @@ -import server from '@server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; -import { CertificatedPhoneToken, LoginToken } from '@utils/jwt'; -import * as PreOrder from '@DTO/preOrder.dto'; -import * as Order from '@DTO/order.dto'; -import { Prisma } from '@prisma/client'; -import test400 from './400test'; -import seedValues from './seedValues'; -import { ErrorInterface } from '@DTO/index.dto'; - -let app: FastifyInstance; - -const accessToken = new LoginToken(seedValues.user.id).signAccessToken(); -beforeAll(async () => { - app = await server(); -}); - -afterAll(async () => { - await app.close(); -}); - -test('400 test', async () => { - await test400(app, [ - ['/api/preorder', 'POST'], - ['/api/preorder', 'GET'], - ['/api/preorder/1', 'GET'], - ]); -}); - -const preOrderCustomerPhone = '01074185263'; -const current = () => new Date().toISOString(); -let preOrderId: number; -test('preOrder', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/preorder`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: preOrderCustomerPhone, - memo: '얼음 따로 포장해주세요', - orderedFor: current(), - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '얼음 따로 포장해주세요', - }, - ], - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.newPreOrderInterface['Reply']['200']; - preOrderId = body.preOrderId; - expect(body.preOrderId).toBeDefined(); -}); - -let preOrderWithoutMemoId: number; -test('preOrder without memo', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/preorder`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - phone: preOrderCustomerPhone, - orderedFor: current(), - totalPrice: Prisma.Decimal.mul(seedValues.menu[0].price, 3), - menus: [ - { - id: seedValues.menu[0].id, - count: 3, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - ], - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.newPreOrderInterface['Reply']['200']; - expect(body.preOrderId).toBeDefined(); - preOrderWithoutMemoId = body.preOrderId; -}); - -let preOrderId2: number; -test('preOrder 2', async () => { - const response = await app.inject({ - method: 'POST', - url: `/api/preorder`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - payload: { - phone: preOrderCustomerPhone, - memo: '얼음 따로 포장해주세요', - orderedFor: current(), - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '얼음 따로 포장해주세요', - }, - ], - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.newPreOrderInterface['Reply']['200']; - preOrderId2 = body.preOrderId; - expect(body.preOrderId).toBeDefined(); -}); - -let preOrderData: any; -test('get preOrder', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/preorder/${preOrderId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.getPreOrderInterface['Reply']['200']; - preOrderData = body; - expect(body.totalPrice).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ).toString() - ); -}); - -test('get preOrder without memo', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/preorder/${preOrderWithoutMemoId}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.getPreOrderInterface['Reply']['200']; - expect(body.totalPrice).toEqual( - Prisma.Decimal.mul(seedValues.menu[0].price, 3).toString() - ); - expect(body.memo).toEqual(''); -}); - -test('get not exist preOrder', async () => { - // preOrderService.test 에서 getPreOrder에서 preOrder가 없을 때 에러 발생 - const response = await app.inject({ - method: 'GET', - url: `/api/preorder/${100}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toEqual('해당하는 예약 주문이 없습니다.'); -}); - -test('get exist preOrder but wrong storeId', async () => { - // preOrderService.test 에서 getPreOrder에서 preOrder가 없을 때 에러 발생 - const response = await app.inject({ - method: 'GET', - url: `/api/preorder/${preOrderId2}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(404); - const body = JSON.parse(response.body) as ErrorInterface; - expect(body.message).toEqual('해당하는 예약 주문이 없습니다.'); -}); - -test('update preOrder2', async () => { - const response = await app.inject({ - method: 'PUT', - url: `/api/preorder`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - payload: { - id: preOrderId2, - phone: preOrderCustomerPhone, - memo: '테스트 변경', - orderedFor: current(), - totalPrice: Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ), - menus: [ - { - id: seedValues.menu[0].id, - count: 2, - options: [ - seedValues.option[0].id, - seedValues.option[2].id, - seedValues.option[4].id, - ], - }, - { - id: seedValues.menu[1].id, - count: 1, - options: [seedValues.option[0].id], - detail: '테스트 변경', - }, - ], - }, - }); - expect(response.statusCode).toBe(201); - const body = JSON.parse( - response.body - ) as PreOrder.updatePreOrderInterface['Reply']['201']; - expect(body).toEqual({ preOrderId: preOrderId2 }); -}); - -test('soft delete preOrder2', async () => { - const response = await app.inject({ - method: 'DELETE', - url: `/api/preorder/${preOrderId2}`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store2.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.softDeletePreOrderInterface['Reply']['200']; - expect(body).toEqual({ preOrderId: preOrderId2 }); -}); - -test('get preorder list', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/preorder?page=1&count=10&date=${current()}`, - - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.getPreOrderListInterface['Reply']['200']; - expect(body.preOrders.length).toBeGreaterThan(1); - const order = body.preOrders[1]; - expect(order.preOrderId).toEqual(preOrderId); - expect(order.totalCount).toEqual(3); -}); - -test('get preorder list without options', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/preorder`, - - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.getPreOrderListInterface['Reply']['200']; - if (body.preOrders.length === 0) return; - expect(body.preOrders.length).toBeGreaterThan(1); - const order = body.preOrders[1]; - expect(order.preOrderId).toEqual(preOrderId); - expect(order.totalPrice).toEqual( - Prisma.Decimal.sum( - Prisma.Decimal.mul(seedValues.menu[0].price, 2), - seedValues.menu[1].price - ).toString() - ); -}); - -test('preOrder to order', async () => { - const orderMenus = preOrderData.orderitems.map((item: any) => { - let optionList: any[] = []; - item.options.forEach((option: any) => { - optionList.push(option.id); - }); - item.options = optionList; - return item; - }); //각 options의 id만 배열로 바꿔줌 - const response = await app.inject({ - method: 'POST', - url: `/api/order`, - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - payload: { - preOrderId: preOrderId, - totalPrice: preOrderData.totalPrice, - menus: orderMenus, - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as Order.newOrderInterface['Reply']['200']; - expect(body.orderId).toBeDefined(); -}); - -test('get preOrder list after order', async () => { - const response = await app.inject({ - method: 'GET', - url: `/api/preorder?page=1&count=10&date=${current()}`, - - headers: { - authorization: `Bearer ${accessToken}`, - storeid: seedValues.store.id.toString(), - }, - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse( - response.body - ) as PreOrder.getPreOrderListInterface['Reply']['200']; - expect(body.preOrders.length).toEqual(1); - const order = body.preOrders[0]; - expect(order.preOrderId).toEqual(preOrderWithoutMemoId); - expect(order.totalPrice).toEqual( - Prisma.Decimal.mul(seedValues.menu[0].price, 3).toString() - ); -}); diff --git a/test/integration/seedValues.ts b/test/integration/seedValues.ts deleted file mode 100644 index 5b6827d..0000000 --- a/test/integration/seedValues.ts +++ /dev/null @@ -1,133 +0,0 @@ -const user = { - id: 1, - businessRegistrationNumber: '5133001104', - phoneNumber: '01012345678', -}; - -const store = { - id: 1, - userId: 1, - name: '소예다방', - address: '고려대 근처', - defaultOpeningHours: [ - { - yoil: '일', - start: null, - end: null, //휴무일은 시작시간과 종료시간 모두 null 이어야함 - }, - { - yoil: '월', - start: '09:00', - end: '18:00', - }, - ], -}; - -const store2 = { - id: 2, - userId: 1, - name: '소예다방', - address: '고려대 근처', - defaultOpeningHours: [ - { - yoil: '일', - start: null, - end: null, //휴무일은 시작시간과 종료시간 모두 null 이어야함 - }, - { - yoil: '월', - start: '09:00', - end: '18:00', - }, - ], -}; - -const category = [ - { - id: 1, - storeId: 1, - name: '커피', - sort: 1, - }, - { - id: 2, - storeId: 1, - name: '티&에이드', - sort: 2, - }, -]; - -const option = [ - { - optionName: 'ice', - optionPrice: 0, - optionCategory: '온도', - storeId: 1, - }, - { - optionName: 'hot', - optionPrice: 0, - optionCategory: '온도', - storeId: 1, - }, - { - optionName: '케냐', - optionPrice: 0, - optionCategory: '원두', - storeId: 1, - }, - { - optionName: '콜롬비아', - optionPrice: 300, - optionCategory: '원두', - storeId: 1, - }, - { - optionName: '1샷 추가', - optionPrice: 500, - optionCategory: '샷', - storeId: 1, - }, - { - optionName: '연하게', - optionPrice: 0, - optionCategory: '샷', - storeId: 1, - }, -].map((option, index) => ({ - ...option, - id: index + 1, -})); - -const menu = [ - { - id: 1, - categoryId: 1, - name: '아메리카노', - price: 2000, - sort: 1, - }, - { - id: 2, - categoryId: 1, - name: '카페라떼', - price: 3000, - sort: 2, - }, - { - id: 3, - categoryId: 2, - name: '아이스티', - price: 2500, - sort: 3, - }, -]; - -export default { - user, - store, - category, - option, - menu, - store2, -}; diff --git a/test/integration/storeService.test.ts b/test/integration/storeService.test.ts deleted file mode 100644 index ba5e55b..0000000 --- a/test/integration/storeService.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import server from '@server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; -import { CertificatedPhoneToken } from '@utils/jwt'; -import userService from '@services/userService'; -import * as Store from '@DTO/store.dto'; -import * as Menu from '@DTO/menu.dto'; -import test400 from './400test'; -import seedValues from './seedValues'; -import { LoginToken } from '@utils/jwt'; - -let app: FastifyInstance; - -const phone = '01011112222'; -const businessRegistrationNumber = '5133001104'; -let accessToken: string; - -beforeAll(async () => { - app = await server(); - const certificatedPhoneToken = new CertificatedPhoneToken(phone).sign(); - accessToken = ( - await userService.login({ - businessRegistrationNumber, - certificatedPhoneToken, - }) - ).accessToken; -}); - -afterAll(async () => { - await app.close(); -}); - -const storeName = '테스트 매장'; -const storeAddress = '서울시 강남구'; -const defaultOpeningHours: Array<{ - day: string; - open: string | null; - close: string | null; -}> = [ - { - day: '일', - open: null, - close: null, - }, - ...['월', '화', '수', '목', '금', '토'].map((day) => ({ - day: day, - open: '09:00', - close: '18:00', - })), - { - day: '공휴일', - open: '10:00', - close: '17:00', - }, -]; - -test('400 test', async () => { - await test400(app, [ - ['/api/store', 'POST'], - ['/api/store', 'GET'], - ]); -}); - -let storeId: number; -test('new store', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/store', - payload: { - name: storeName, - address: storeAddress, - openingHours: defaultOpeningHours, - }, - headers: { - authorization: accessToken, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Store.newStoreInterface['Reply']['200']; - expect(body).toHaveProperty('storeId'); - storeId = body.storeId; -}); - -test('default options', async () => { - const response = await app.inject({ - method: 'GET', - url: '/api/menu/option', - headers: { - authorization: accessToken, - storeid: storeId.toString(), - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Menu.getOptionListInterface['Reply']['200']; - expect(body).toHaveProperty('option'); - expect(body.option).toEqual([ - { - optionType: '온도', - options: [ - { - id: expect.any(Number), - name: 'ice', - price: '0', - }, - { - id: expect.any(Number), - name: 'hot', - price: '0', - }, - ], - }, - { - optionType: '원두', - options: [ - { - id: expect.any(Number), - name: '케냐', - price: '0', - }, - { - id: expect.any(Number), - name: '콜롬비아', - price: '300', - }, - ], - }, - { - optionType: '샷', - options: [ - { - id: expect.any(Number), - name: '1샷 추가', - price: '500', - }, - { - id: expect.any(Number), - name: '연하게', - price: '0', - }, - ], - }, - ]); -}); - -test('get store list', async () => { - const response = await app.inject({ - method: 'GET', - url: '/api/store', - headers: { - authorization: accessToken, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as Store.storeListInterface['Reply']['200']; - expect(body).toHaveProperty('stores'); - expect(body.stores.length).toBeGreaterThan(0); - const lastStore = body.stores[body.stores.length - 1]; - expect(lastStore).toHaveProperty('storeId'); - expect(lastStore).toHaveProperty('name'); - expect(lastStore).toHaveProperty('address'); - expect(lastStore).toEqual({ - storeId: storeId, - name: storeName, - address: storeAddress, - }); -}); - -test('update store', async () => { - const response = await app.inject({ - method: 'PUT', - url: '/api/store', - payload: { - storeId, - name: '테스트 변경', - address: '테스트 변경', - openingHours: defaultOpeningHours, - }, - headers: { - authorization: accessToken, - storeid: storeId.toString(), - }, - }); - expect(response.statusCode).toBe(201); - - const body = JSON.parse( - response.body - ) as Store.updateStoreInterface['Reply']['201']; - expect(body).toHaveProperty('storeId'); - expect(body.storeId).toBe(storeId); -}); diff --git a/test/integration/testValues.ts b/test/integration/testValues.ts new file mode 100644 index 0000000..98e620b --- /dev/null +++ b/test/integration/testValues.ts @@ -0,0 +1,71 @@ +import { LoginToken } from '@utils/jwt'; + +type KeyOfType = keyof { + [Key in keyof Type as Type[Key] extends ValueType ? Key : never]: any; +}; + +class Values { + private static instance: Values | null = null; + private constructor() { } + public static getInstance(): Values { + if (this.instance === null) { + this.instance = new Values(); + } + return this.instance; + } + private accessToken = new LoginToken(1).signAccessToken(); + public userHeader = { + authorization: this.accessToken, + }; + public storeHeader = { + authorization: this.accessToken, + storeid: 1, + }; + //stocks + public waterId: number = 1; + public coffeeBeanId: number = 2; + public milkId: number = 3; + public grapefruitId: number = 0; + public sugarId: number = 0; + public sparklingWaterId: number = 0; + public lemonId: number = 0; + public preservedGrapefruitId: number = 0; + public preservedLemonId: number = 0; + + //categories + public coffeeCategoryId: number = 1; + public adeCategoryId: number = 0; + + //options + public iceOptionId: number = 1; + public hotOptionId: number = 2; + public bean1OptionId: number = 3; + public bean2OptionId: number = 4; + public shotPlusOptionId: number = 5; + public shotMinusOptionId: number = 6; + + //menus + public americanoId: number = 1; + public latteId: number = 2; + public grapefruitAdeId: number = 0; + public lemonAdeId: number = 0; + + //mileage + public customerPhone1: string = '01012345678'; + public customerPhone2: string = '01087654321'; + public wrongPhone: string = '010123'; + public mileageId1: number = 0; + public mileageId2: number = 0; + + //orders + public firstPreorderId: number = 0; + public secondPreorderId: number = 0; + public firstOrderId: number = 0; + public secondOrderId: number = 0; + + setValues(name: KeyOfType, value: number) { + this[name] = value; + } +} + +export default Values.getInstance(); diff --git a/test/integration/userService.test.ts b/test/integration/userService.test.ts deleted file mode 100644 index 4a30bab..0000000 --- a/test/integration/userService.test.ts +++ /dev/null @@ -1,183 +0,0 @@ -import server from '../../src/server'; -import { FastifyInstance } from 'fastify'; -import { afterAll, beforeAll, describe, expect, test } from '@jest/globals'; -import * as User from '../../src/DTO/user.dto'; -import test400 from './400test'; -import { ErrorInterface } from '@DTO/index.dto'; - -let app: FastifyInstance; - -beforeAll(async () => { - app = await server(); -}); - -afterAll(async () => { - await app.close(); -}); - -const phone = '01012345678'; -const businessRegistrationNumber = '5133001104'; -let tokenForCertificatePhone: string; -let certificatedPhoneToken: string; -let accessToken: string; -let refreshToken: string; - -describe('login', () => { - test('ping', async () => { - const response = await app.inject({ - method: 'GET', - url: '/api/ping', - }); - expect(response.statusCode).toBe(200); - const body = JSON.parse(response.body) as { data: string }; - expect(body.data).toBe('pong'); - }); - test('400 test', async () => { - await test400(app, [ - ['/api/user/phone', 'POST'], - ['/api/user/phone/certificationCode', 'POST'], - ['/api/user/login', 'POST'], - ['/api/user/refresh', 'POST'], - ]); - }); - - test('send CertificationCode', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/phone', - payload: { - phone: phone, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as User.phoneInterface['Reply']['200']; - expect(body).toHaveProperty('tokenForCertificatePhone'); - tokenForCertificatePhone = body.tokenForCertificatePhone as string; - }); - - test('certificate phone:success', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/phone/certificationCode', - payload: { - phone: phone, - certificationCode: '123456', - phoneCertificationToken: tokenForCertificatePhone, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as User.certificatePhoneInterface['Reply']['200']; - expect(body).toHaveProperty('certificatedPhoneToken'); - certificatedPhoneToken = body.certificatedPhoneToken; - }); - - test('certificate phone:fail', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/phone/certificationCode', - payload: { - phone: phone, - certificationCode: '111222', - phoneCertificationToken: tokenForCertificatePhone, - }, - }); - expect(response.statusCode).toBe(401); - }); - - test('new user: fail', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/login', - payload: { - businessRegistrationNumber: businessRegistrationNumber, - certificatedPhoneToken: 'asdf', - }, - }); - expect(response.statusCode).toBe(401); - }); - - test('new user', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/login', - payload: { - businessRegistrationNumber: businessRegistrationNumber, - certificatedPhoneToken: certificatedPhoneToken, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as User.loginInterface['Reply']['200']; - expect(body).toHaveProperty('accessToken'); - expect(body).toHaveProperty('refreshToken'); - accessToken = body.accessToken; - refreshToken = body.refreshToken; - }); - - test('login', async () => { - //유효기간이 다른 accessToken을 발급받기 위해 1초 대기 - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const response = await app.inject({ - method: 'POST', - url: '/api/user/login', - payload: { - businessRegistrationNumber: businessRegistrationNumber, - certificatedPhoneToken: certificatedPhoneToken, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as User.loginInterface['Reply']['200']; - expect(body).toHaveProperty('accessToken'); - expect(body).toHaveProperty('refreshToken'); - expect(body.accessToken).not.toBe(accessToken); - expect(body.refreshToken).not.toBe(refreshToken); - accessToken = body.accessToken; - refreshToken = body.refreshToken; - }); - - test('refresh token:fail', async () => { - const response = await app.inject({ - method: 'POST', - url: '/api/user/refresh', - headers: { - Authorization: `Bearer ${refreshToken + 1}`, - }, - }); - expect(response.statusCode).toBe(401); - }); - - test('refresh token', async () => { - //유효기간이 다른 accessToken을 발급받기 위해 1초 대기 - await new Promise((resolve) => setTimeout(resolve, 1000)); - const response = await app.inject({ - method: 'POST', - url: '/api/user/refresh', - headers: { - Authorization: `Bearer ${refreshToken}`, - }, - }); - expect(response.statusCode).toBe(200); - - const body = JSON.parse( - response.body - ) as User.refreshInterface['Reply']['200']; - expect(body).toHaveProperty('accessToken'); - expect(body).toHaveProperty('refreshToken'); - expect(body.accessToken).not.toBe(accessToken); - expect(body.refreshToken).not.toBe(refreshToken); - accessToken = body.accessToken as string; - refreshToken = body.refreshToken as string; - }); -}); diff --git a/tsconfig.json b/tsconfig.json index 09b7b86..5a08020 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { "strict": true, - "target": "es6", - "module": "es6", - "moduleResolution": "node", + "target": "es2022", + "module": "es2022", + "moduleResolution": "Node", "outDir": "build", "esModuleInterop": true, "baseUrl": ".", @@ -19,6 +19,7 @@ "@routes/*": ["src/api/routes/*"], "@hooks/*": ["src/api/hooks/*"], "@server": ["src/server"], + "@seeding/*": ["seeding/*"], } }, "include": [