diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 429d265..817e50b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -18,14 +18,15 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} + - name: Building using Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} cache: "yarn" cache-dependency-path: "**/yarn.lock" + - run: cp ~/envs/.env . - run: yarn - - run: yarn run build + - run: yarn build - run: pm2 stop all - run: pm2 delete all - run: pm2 save --force diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80ac11f..daa1c02 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ on: branches: [master] jobs: - build: + test: runs-on: self-hosted defaults: @@ -18,12 +18,12 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Use Node.js ${{ matrix.node-version }} + - name: Testing using Node.js ${{ matrix.node-version }} uses: actions/setup-node@v2 with: node-version: ${{ matrix.node-version }} cache: "yarn" cache-dependency-path: "**/yarn.lock" + - run: cp ~/envs/.env . - run: yarn - - run: yarn run build - run: yarn test diff --git a/cattleia-backend/.env.example b/cattleia-backend/.env.example index 80f8988..3afa1c4 100644 --- a/cattleia-backend/.env.example +++ b/cattleia-backend/.env.example @@ -2,10 +2,12 @@ PORT= JWT_ACCESS= JWT_REFRESH= MONGODB_URI= +MONGODB_URI_TEST= DAPP_TOKEN= INFURA_MAIN= INFURA_ROPSTEN= INFURA_RINKEBY= GANACHE= STRIPE_SECRET= -STRIPE_PUBLISHABLE= \ No newline at end of file +STRIPE_PUBLISHABLE= +TEST_PRIVATE_KEY \ No newline at end of file diff --git a/cattleia-backend/jest.config.js b/cattleia-backend/jest.config.js index 266f90f..d5ebae2 100644 --- a/cattleia-backend/jest.config.js +++ b/cattleia-backend/jest.config.js @@ -1,3 +1,5 @@ +require("dotenv").config(); + /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { preset: "ts-jest", diff --git a/cattleia-backend/package.json b/cattleia-backend/package.json index cea1cfc..0ef9161 100644 --- a/cattleia-backend/package.json +++ b/cattleia-backend/package.json @@ -6,7 +6,7 @@ "scripts": { "start": "cross-env NODE_ENV=production node dist", "dev": "cross-env NODE_ENV=development ts-node-dev --respawn src", - "test": "cross-env NODE_ENV=test jest", + "test": "cross-env NODE_ENV=test jest --runInBand", "build": "tsc" }, "repository": { diff --git a/cattleia-backend/src/app.ts b/cattleia-backend/src/app.ts index 68c4856..f13a11e 100644 --- a/cattleia-backend/src/app.ts +++ b/cattleia-backend/src/app.ts @@ -1,8 +1,8 @@ +import cookieParser from "cookie-parser"; import express from "express"; import morgan from "morgan"; -import cors from "cors"; -import cookieParser from "cookie-parser"; import config from "./config"; +import cors from "cors"; import { initRoles, initRanks } from "./libs"; //Inits @@ -25,7 +25,11 @@ app.use( credentials: true, }) ); -app.use(morgan("dev")); + +if (config.NODE_ENV === "development") { + app.use(morgan("dev")); +} + app.use(cookieParser()); app.use(express.urlencoded({ extended: false })); app.use(express.json()); diff --git a/cattleia-backend/src/config/index.ts b/cattleia-backend/src/config/index.ts index a79b231..555fbb0 100644 --- a/cattleia-backend/src/config/index.ts +++ b/cattleia-backend/src/config/index.ts @@ -1,5 +1,6 @@ export default { PORT: process.env.PORT || 4000, + NODE_ENV: process.env.NODE_ENV, JWT: { ACCESS: process.env.JWT_ACCESSS || "acc", REFRESH: process.env.JWT_REFRESH || "ref", diff --git a/cattleia-backend/src/controllers/auth.controller.ts b/cattleia-backend/src/controllers/auth.controller.ts index 66c71a5..2d15155 100644 --- a/cattleia-backend/src/controllers/auth.controller.ts +++ b/cattleia-backend/src/controllers/auth.controller.ts @@ -32,7 +32,7 @@ export const signIn = async ( if (_user) { const matchPass = await comparePassword(_user.password, password); if (!matchPass) { - return res.json({ + return res.status(401).json({ ok: false, error: errors.wrongUserOrPassword, }); @@ -110,7 +110,7 @@ export const signUp = async ( if (role) { const foundRole: Role | null = await RoleModel.findOne({ name: role }); if (foundRole) _user.role = foundRole; - else return res.json({ ok: false }); + else return res.json({ ok: false, error: errors.invalidRole }); } else { const roleD: Role | null = await RoleModel.findOne({ name: "user" }); _user.role = roleD!; diff --git a/cattleia-backend/src/controllers/index.controller.ts b/cattleia-backend/src/controllers/index.controller.ts index f1e23fb..aac2b45 100644 --- a/cattleia-backend/src/controllers/index.controller.ts +++ b/cattleia-backend/src/controllers/index.controller.ts @@ -20,17 +20,17 @@ export const refreshToken = async ( ): Promise => { const token = req.cookies.jid; if (!token) - return res.json({ + return res.status(400).json({ ok: false, - accessToken: "", + error: errors.noAuthToken, }); try { const payload = verify(token, config.JWT.REFRESH) as Payload; const user: User | null = await UserModel.findById(payload._id); if (!user || user.tokenVersion !== payload.tokenVersion) - return res.json({ + return res.status(400).json({ ok: false, - accessToken: "", + error: errors.invalidAuthToken, }); res.cookie("jid", createRefreshToken(user), cookieConf); return res.json({ @@ -38,9 +38,9 @@ export const refreshToken = async ( accessToken: createAcessToken(user), }); } catch (error) { - return res.json({ + return res.status(400).json({ ok: false, - accessToken: "", + error: errors.invalidAuthToken, }); } }; @@ -52,7 +52,7 @@ export const revokeRefreshTokens = async ( const id = req.id; const user: User | null = await UserModel.findById(id); if (!user) - return res.json({ + return res.status(400).json({ ok: false, error: errors.invalidIDorNoWallet(id), }); diff --git a/cattleia-backend/src/controllers/payment.controller.ts b/cattleia-backend/src/controllers/payment.controller.ts index 5411bd8..4d97f53 100644 --- a/cattleia-backend/src/controllers/payment.controller.ts +++ b/cattleia-backend/src/controllers/payment.controller.ts @@ -1,20 +1,31 @@ import Stripe from "stripe"; import config from "../config"; import { Request, Response } from "express"; +import { errors } from "../libs"; const stripe = new Stripe(config.STRIPE_SECRET, { apiVersion: "2020-08-27", typescript: true, }); -export const stripePaymentIntent = async (req: Request, res: Response) => { +export const stripePaymentIntent = async ( + req: Request, + res: Response +): Promise => { const { amount } = req.body; + if (amount < 2000 || !amount) { + return res.status(400).json({ + ok: false, + error: errors.invalidAmount, + }); + } + const paymentIntent = await stripe.paymentIntents.create({ amount, currency: "cop", }); - res.json({ + return res.json({ ok: true, clientSecret: paymentIntent.client_secret, }); @@ -22,6 +33,7 @@ export const stripePaymentIntent = async (req: Request, res: Response) => { export const publishableKey = (_req: Request, res: Response) => { res.json({ + ok: true, publishableKey: config.STRIPE_PUBLISHABLE, }); }; diff --git a/cattleia-backend/src/controllers/web3.controller.ts b/cattleia-backend/src/controllers/web3.controller.ts index 963f481..76c4103 100644 --- a/cattleia-backend/src/controllers/web3.controller.ts +++ b/cattleia-backend/src/controllers/web3.controller.ts @@ -31,12 +31,12 @@ export const access = async ( const { password } = req.body; const _user: User | null = await UserModel.findById(id).populate("account"); if (!_user || !_user.account.payload) - return res.json({ + return res.status(400).json({ ok: false, error: errors.invalidIDorNoWallet(id), }); if (!(await comparePassword(_user.account.password, password))) - return res.json({ + return res.status(401).json({ ok: false, error: errors.wrongWalletPassword, }); @@ -55,10 +55,16 @@ export const createAccount = async ( const _user = await UserModel.findById(_id).populate("account"); - if (!_user || _user.account.payload) - return res.json({ + if (!_user) + return res.status(400).json({ ok: false, - error: errors.invalidIDorNoWallet(_id), + error: errors.invalidID(_id), + }); + + if (_user.account.payload) + return res.status(400).json({ + ok: false, + error: errors.hasWalletAccount, }); const account = web3.eth.accounts.create(getNonce()); @@ -72,7 +78,7 @@ export const createAccount = async ( if (!_account) return res.json({ ok: false, - error: errors.invalidIDorNoWallet(_id), + error: errors.noWalletAccount, }); _account.password = await encryptPassword(password); @@ -96,10 +102,16 @@ export const importAccount = async ( const _user: User | null = await UserModel.findById(id).populate("account"); - if (!_user || _user.account.payload) - return res.json({ + if (!_user) + return res.status(400).json({ ok: false, - error: errors.invalidIDorNoWallet(id), + error: errors.invalidID(id), + }); + + if (_user.account.payload) + return res.status(400).json({ + ok: false, + error: errors.hasWalletAccount, }); let { privateKey, password } = req.body; @@ -147,14 +159,14 @@ export const getPrivateKey = async ( const _user = await UserModel.findById(_id).populate("account"); if (!_user || !_user.account.payload) { - return res.json({ + return res.status(400).json({ ok: false, error: errors.invalidIDorNoWallet(_id), }); } if (!(await comparePassword(_user.account.password, password))) - return res.json({ + return res.status(401).json({ ok: false, error: errors.wrongWalletPassword, }); @@ -180,13 +192,13 @@ export const transferTo = async ( const _user: User | null = await UserModel.findById(id).populate("account"); if (!_user || !_user.account.payload) - return res.json({ + return res.status(400).json({ ok: false, error: errors.invalidIDorNoWallet(id), }); if (!(await comparePassword(_user.account.password, password))) - return res.json({ + return res.status(401).json({ ok: false, error: errors.wrongWalletPassword, }); @@ -223,7 +235,7 @@ export const transferTo = async ( errorStack(stack, e); } - return res.json({ + return res.status(400).json({ ok: false, error: stack[0], }); diff --git a/cattleia-backend/src/libs/errors.ts b/cattleia-backend/src/libs/errors.ts index ddc9261..f7a18fa 100644 --- a/cattleia-backend/src/libs/errors.ts +++ b/cattleia-backend/src/libs/errors.ts @@ -24,6 +24,14 @@ export const errors = { message: "Invalid auth token.", code: 9090, }, + invalidRole: { + message: "Invalid role.", + code: 8020, + }, + invalidAmount: { + message: "Invalid amount, must be greater than 0.5$", + code: 6060, + }, userAlreadyTaken: { message: "Username already taken.", code: 5342, @@ -37,15 +45,24 @@ export const errors = { code: 5040, }, noWalletAccount: { - message: "There's no wallet accout for this user.", - code: 4050, + message: "There's no wallet account for this user.", + code: 4065, + }, + hasWalletAccount: { + message: "There's already a wallet account for this user.", + code: 4055, }, noAdmin: { message: "You need to be an admin.", code: 4045, }, + + invalidID: (id: string) => ({ + message: `There's no user with ID <${id}>.`, + code: 4040, + }), invalidIDorNoWallet: (id: string) => ({ message: `There's no user with ID <${id}> or the user has no wallet account.`, - code: 4040, + code: 4020, }), }; diff --git a/cattleia-backend/src/middlewares/index.ts b/cattleia-backend/src/middlewares/index.ts index ebfd4fa..1409038 100644 --- a/cattleia-backend/src/middlewares/index.ts +++ b/cattleia-backend/src/middlewares/index.ts @@ -31,7 +31,7 @@ export const validateToken = ( } else { return res.status(401).json({ ok: false, - error: errors.invalidAuthToken, + error: errors.noAuthToken, }); } } diff --git a/cattleia-backend/src/models/account.ts b/cattleia-backend/src/models/account.ts index 182fc1e..dfaad6e 100644 --- a/cattleia-backend/src/models/account.ts +++ b/cattleia-backend/src/models/account.ts @@ -15,9 +15,4 @@ const accountSchema = new Schema( } ); -export const AccountModel = model( - "Account", - accountSchema, - "Account", - true -); +export const AccountModel = model("Account", accountSchema); diff --git a/cattleia-backend/src/models/rank.ts b/cattleia-backend/src/models/rank.ts index eaec7ff..62ff90a 100644 --- a/cattleia-backend/src/models/rank.ts +++ b/cattleia-backend/src/models/rank.ts @@ -11,4 +11,4 @@ const rankSchema = new Schema( } ); -export const RankModel = model("Rank", rankSchema, "Rank", true); +export const RankModel = model("Rank", rankSchema); diff --git a/cattleia-backend/src/models/role.ts b/cattleia-backend/src/models/role.ts index bab07cf..53562a0 100644 --- a/cattleia-backend/src/models/role.ts +++ b/cattleia-backend/src/models/role.ts @@ -10,4 +10,4 @@ const roleSchema = new Schema( } ); -export const RoleModel = model("Role", roleSchema, "Role", true); +export const RoleModel = model("Role", roleSchema); diff --git a/cattleia-backend/src/models/user.ts b/cattleia-backend/src/models/user.ts index fc770ef..02454e7 100644 --- a/cattleia-backend/src/models/user.ts +++ b/cattleia-backend/src/models/user.ts @@ -48,4 +48,4 @@ const userSchema: Schema = new Schema( } ); -export const UserModel = model("User", userSchema, "User", true); +export const UserModel = model("User", userSchema); diff --git a/cattleia-backend/src/test/jest/admin.spec.ts b/cattleia-backend/src/test/jest/admin.spec.ts index 94afca1..2e14705 100644 --- a/cattleia-backend/src/test/jest/admin.spec.ts +++ b/cattleia-backend/src/test/jest/admin.spec.ts @@ -1,9 +1,6 @@ -import request from "supertest"; import mongoose from "mongoose"; -import app from "../../app"; import { errors } from "../../libs"; - -const api = request(app); +import { api } from "./helper"; afterEach(() => { mongoose.connection.close(); diff --git a/cattleia-backend/src/test/jest/auth.spec.ts b/cattleia-backend/src/test/jest/auth.spec.ts new file mode 100644 index 0000000..d0fc54b --- /dev/null +++ b/cattleia-backend/src/test/jest/auth.spec.ts @@ -0,0 +1,133 @@ +import mongoose from "mongoose"; +import { encryptPassword, errors } from "../../libs"; +import { api, user, user2 } from "./helper"; +import { UserModel } from "../../models"; + +beforeEach(async () => { + await UserModel.deleteMany({}); + const _user = new UserModel({ + ...user2, + password: await encryptPassword(user2.password), + }); + await _user.save(); +}); + +afterAll(() => { + mongoose.connection.close(); +}); + +describe("POST /api/v1/auth/sign-up", () => { + test("should register an user with default role (user).", async () => { + const res = await api.post("/api/v1/auth/sign-up").send(user); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.user.id).toBeDefined(); + expect(res.body.user.role).toEqual("user"); + expect(res.body.user.token).toBeDefined(); + expect(res.body.user.account).toBeDefined(); + expect(res.body.user.rank).toBeDefined(); + expect(res.body.user.email).toEqual(user.email); + expect(res.body.user.name).toEqual(user.name); + expect(res.body.user.userName).toEqual(user.userName); + }); + + test("should register an user with role = user.", async () => { + const res = await api + .post("/api/v1/auth/sign-up") + .send({ ...user, role: "user" }); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.user.id).toBeDefined(); + expect(res.body.user.role).toEqual("user"); + expect(res.body.user.token).toBeDefined(); + expect(res.body.user.account).toBeDefined(); + expect(res.body.user.rank).toBeDefined(); + expect(res.body.user.email).toEqual(user.email); + expect(res.body.user.name).toEqual(user.name); + expect(res.body.user.userName).toEqual(user.userName); + }); + + test("should register an user with role = admin.", async () => { + const res = await api + .post("/api/v1/auth/sign-up") + .send({ ...user, role: "admin" }); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.user.id).toBeDefined(); + expect(res.body.user.role).toEqual("admin"); + expect(res.body.user.token).toBeDefined(); + expect(res.body.user.account).toBeDefined(); + expect(res.body.user.rank).toBeDefined(); + expect(res.body.user.email).toEqual(user.email); + expect(res.body.user.name).toEqual(user.name); + expect(res.body.user.userName).toEqual(user.userName); + }); + + test("should respond with 'invalid role' error.", async () => { + const res = await api + .post("/api/v1/auth/sign-up") + .send({ ...user, role: "test" }); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidRole.message); + expect(res.body.error.code).toEqual(errors.invalidRole.code); + }); + + test("should respond with 'userName taken' error.", async () => { + const res = await api.post("/api/v1/auth/sign-up").send(user2); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.userAlreadyTaken.message); + expect(res.body.error.code).toEqual(errors.userAlreadyTaken.code); + }); +}); + +describe("POST /api/v1/auth/sign-in", () => { + test("should be able to login.", async () => { + await api.post("/api/v1/auth/sign-up").send(user); + const res = await api + .post("/api/v1/auth/sign-in") + .send({ userName: user.userName, password: user.password }); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.user.id).toBeDefined(); + expect(res.body.user.role).toEqual("user"); + expect(res.body.user.token).toBeDefined(); + expect(res.body.user.account).toBeDefined(); + expect(res.body.user.rank).toBeDefined(); + expect(res.body.user.email).toEqual(user.email); + expect(res.body.user.name).toEqual(user.name); + expect(res.body.user.userName).toEqual(user.userName); + }); + + test("shouldn't be able to login with wrong username.", async () => { + await api.post("/api/v1/auth/sign-up").send(user); + const res = await api + .post("/api/v1/auth/sign-in") + .send({ userName: "wrong", password: user.password }); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongUserOrPassword.message); + expect(res.body.error.code).toEqual(errors.wrongUserOrPassword.code); + }); + + test("shouldn't be able to login with wrong password.", async () => { + await api.post("/api/v1/auth/sign-up").send(user); + const res = await api + .post("/api/v1/auth/sign-in") + .send({ userName: user.userName, password: "wrong" }); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongUserOrPassword.message); + expect(res.body.error.code).toEqual(errors.wrongUserOrPassword.code); + }); + + test("shouldn't be able to login with wrong password and wrong username.", async () => { + await api.post("/api/v1/auth/sign-up").send(user); + const res = await api + .post("/api/v1/auth/sign-in") + .send({ userName: "wrong1", password: "wrong2" }); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongUserOrPassword.message); + expect(res.body.error.code).toEqual(errors.wrongUserOrPassword.code); + }); +}); diff --git a/cattleia-backend/src/test/jest/helper.ts b/cattleia-backend/src/test/jest/helper.ts new file mode 100644 index 0000000..cb40fe8 --- /dev/null +++ b/cattleia-backend/src/test/jest/helper.ts @@ -0,0 +1,62 @@ +import request from "supertest"; +import app from "../../app"; + +export const api = request(app); + +const baseUrl = "/api/v1"; + +export const user = { + name: "Testing", + email: "test@email.com", + userName: "test", + password: "1234", +}; + +export const user2 = { + name: "Testing2", + email: "test2@email.com", + userName: "test2", + password: "1234", +}; + +export const user3 = { + name: "Testing3", + email: "test3@email.com", + userName: "test3", + password: "1234", +}; + +export const invalidToken = async ( + url: string, + token: boolean +): Promise => { + return await api + .post(baseUrl + url) + .set({ Authorization: token ? "bearer adsfsdfdf" : "" }) + .send({ password: "1234" }); +}; + +export const simplePost = async ( + url: string, + data: object +): Promise => { + return await api.post(baseUrl + url).send(data); +}; + +export const postWithToken = async ( + url1: string, + url2: string, + data1: object, + data2: object +): Promise<{ res: request.Response; res1: request.Response }> => { + const res1 = await api.post(baseUrl + url1).send(data1); + const res = await api + .post(baseUrl + url2) + .set({ Authorization: `bearer ${res1.body.user.token}` }) + .send(data2); + + return { + res, + res1, + }; +}; diff --git a/cattleia-backend/src/test/jest/index.spec.ts b/cattleia-backend/src/test/jest/index.spec.ts index e902beb..4ae81fd 100644 --- a/cattleia-backend/src/test/jest/index.spec.ts +++ b/cattleia-backend/src/test/jest/index.spec.ts @@ -1,8 +1,5 @@ -import request from "supertest"; import mongoose from "mongoose"; -import app from "../../app"; - -const api = request(app); +import { api } from "./helper"; afterEach(() => { mongoose.connection.close(); diff --git a/cattleia-backend/src/test/jest/payment.spec.ts b/cattleia-backend/src/test/jest/payment.spec.ts new file mode 100644 index 0000000..ccf70eb --- /dev/null +++ b/cattleia-backend/src/test/jest/payment.spec.ts @@ -0,0 +1,55 @@ +import mongoose from "mongoose"; +import config from "../../config"; +import { errors } from "../../libs"; +import { api } from "./helper"; + +afterEach(async () => { + await mongoose.connection.close(); +}); + +describe("GET /api/v1/payment/get-publishable-key", () => { + test("should respond with a stripe publishableKey.", async () => { + const res = await api.get("/api/v1/payment/get-publishable-key"); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.publishableKey).toEqual(config.STRIPE_PUBLISHABLE); + }); +}); + +describe("POST /api/v1/payment/create-payment-intent", () => { + test("should respond with a stripe client-secret.", async () => { + const res = await api + .post("/api/v1/payment/create-payment-intent") + .withCredentials() + .set({ Authorization: `Bearer ${config.STRIPE_SECRET}` }) + .send({ + amount: 1000000, + }); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.clientSecret).toBeDefined(); + }); + + test("should respond with invalid amount error (amount defined).", async () => { + const res = await api + .post("/api/v1/payment/create-payment-intent") + .set({ Authorization: `Bearer ${config.STRIPE_SECRET}` }) + .send({ + amount: 1000, + }); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAmount.message); + expect(res.body.error.code).toEqual(errors.invalidAmount.code); + }); + test("should respond with invalid amount error (amount undefined).", async () => { + const res = await api + .post("/api/v1/payment/create-payment-intent") + .set({ Authorization: `Bearer ${config.STRIPE_SECRET}` }) + .send({}); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAmount.message); + expect(res.body.error.code).toEqual(errors.invalidAmount.code); + }); +}); diff --git a/cattleia-backend/src/test/jest/web3.spec.ts b/cattleia-backend/src/test/jest/web3.spec.ts new file mode 100644 index 0000000..64f5de5 --- /dev/null +++ b/cattleia-backend/src/test/jest/web3.spec.ts @@ -0,0 +1,308 @@ +import mongoose from "mongoose"; +import { AccountModel, UserModel } from "../../models"; +import { errors } from "../../libs"; +import { + user, + user2, + user3, + invalidToken, + postWithToken, + simplePost, +} from "./helper"; + +beforeAll(async () => { + await AccountModel.deleteMany({}); + await UserModel.deleteMany({}); +}); + +afterAll(async () => { + await mongoose.connection.close(); +}); + +describe("POST /api/v1/web3/create-account", () => { + test("shouldn't be able to create a wallet (token error).", async () => { + const res = await invalidToken("/web3/create-account", true); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAuthToken.message); + expect(res.body.error.code).toEqual(errors.invalidAuthToken.code); + }); + test("shouldn't be able to create a wallet (no-token error).", async () => { + const res = await invalidToken("/web3/create-account", false); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.noAuthToken.message); + expect(res.body.error.code).toEqual(errors.noAuthToken.code); + }); + + test("should be able to create a wallet.", async () => { + const { res } = await postWithToken( + "/auth/sign-up", + "/web3/create-account", + user, + { password: "1234" } + ); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.address).toBeDefined(); + expect(res.body.balance).toBeDefined(); + }); + + test("shouldn't be able to create a wallet (user already has one).", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/create-account", + { password: user.password, userName: user.userName }, + { password: "1234" } + ); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.hasWalletAccount.message); + expect(res.body.error.code).toEqual(errors.hasWalletAccount.code); + }); +}); + +describe("POST /api/v1/web3/import-account", () => { + test("shouldn't be able to import a wallet (token error).", async () => { + const res = await invalidToken("/web3/import-account", true); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAuthToken.message); + expect(res.body.error.code).toEqual(errors.invalidAuthToken.code); + }); + test("shouldn't be able to import a wallet (no-token error).", async () => { + const res = await invalidToken("/web3/import-account", false); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.noAuthToken.message); + expect(res.body.error.code).toEqual(errors.noAuthToken.code); + }); + + test("should be able to import a wallet.", async () => { + const { res } = await postWithToken( + "/auth/sign-up", + "/web3/import-account", + user2, + { password: "1234", privateKey: process.env.TEST_PRIVATE_KEY } + ); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.address).toBeDefined(); + expect(res.body.balance).toBeDefined(); + }); + + test("shouldn't be able to import a wallet (user already has one).", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/import-account", + { password: user2.password, userName: user2.userName }, + { password: "1234", privateKey: process.env.TEST_PRIVATE_KEY } + ); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.hasWalletAccount.message); + expect(res.body.error.code).toEqual(errors.hasWalletAccount.code); + }); +}); + +describe("POST /api/v1/web3/access", () => { + test("shouldn't be able to access to wallet (token error).", async () => { + const res = await invalidToken("/web3/access", true); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAuthToken.message); + expect(res.body.error.code).toEqual(errors.invalidAuthToken.code); + }); + test("shouldn't be able to access to wallet (no-token error).", async () => { + const res = await invalidToken("/web3/access", false); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.noAuthToken.message); + expect(res.body.error.code).toEqual(errors.noAuthToken.code); + }); + + test("should be able to access to wallet.", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/access", + { password: user2.password, userName: user2.userName }, + { password: "1234" } + ); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + }); + + test("shouldn't be able to access to wallet (wrong password).", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/access", + { password: user2.password, userName: user2.userName }, + { password: "1256" } + ); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongWalletPassword.message); + expect(res.body.error.code).toEqual(errors.wrongWalletPassword.code); + }); + test("shouldn't be able to access to wallet (no wallet).", async () => { + const { res, res1 } = await postWithToken( + "/auth/sign-up", + "/web3/access", + { password: user3.password, userName: user3.userName }, + { password: "1256" } + ); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).message + ); + expect(res.body.error.code).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).code + ); + }); +}); + +describe("POST /api/v1/web3/get-key", () => { + test("shouldn't be able to get privateKey (token error).", async () => { + const res = await invalidToken("/web3/get-key", true); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAuthToken.message); + expect(res.body.error.code).toEqual(errors.invalidAuthToken.code); + }); + test("shouldn't be able to get privateKey (no-token error).", async () => { + const res = await invalidToken("/web3/get-key", false); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.noAuthToken.message); + expect(res.body.error.code).toEqual(errors.noAuthToken.code); + }); + + test("should be able to get privateKey.", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/get-key", + { password: user2.password, userName: user2.userName }, + { password: "1234" } + ); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.privateKey).toEqual("0x" + process.env.TEST_PRIVATE_KEY); + }); + + test("shouldn't be able to get privateKey (wrong password).", async () => { + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/get-key", + { password: user2.password, userName: user2.userName }, + { password: "1256" } + ); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongWalletPassword.message); + expect(res.body.error.code).toEqual(errors.wrongWalletPassword.code); + }); + test("shouldn't be able to get privateKey (no wallet).", async () => { + const { res, res1 } = await postWithToken( + "/auth/sign-in", + "/web3/get-key", + { password: user3.password, userName: user3.userName }, + { password: "1256" } + ); + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).message + ); + expect(res.body.error.code).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).code + ); + }); +}); + +jest.setTimeout(30000); + +describe("POST /api/v1/web3/transfer-to", () => { + test("shouldn't be able to transfer eth (token error).", async () => { + const res = await invalidToken("/web3/transfer-to", true); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.invalidAuthToken.message); + expect(res.body.error.code).toEqual(errors.invalidAuthToken.code); + }); + test("shouldn't be able to transfer eth (no-token error).", async () => { + const res = await invalidToken("/web3/transfer-to", false); + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.noAuthToken.message); + expect(res.body.error.code).toEqual(errors.noAuthToken.code); + }); + + test("should be able to transfer eth.", async () => { + const res_ = await simplePost("/auth/sign-in", { + password: user.password, + userName: user.userName, + }); + + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/transfer-to", + { password: user2.password, userName: user2.userName }, + { password: "1234", to: res_.body.user.account.address, value: "0.001" } + ); + expect(res.status).toBe(200); + expect(res.body.ok).toEqual(true); + expect(res.body.to).toEqual("0x" + res_.body.user.account.address); + expect(res.body.hash).toBeDefined(); + expect(res.body.status).toBeDefined(); + }); + + test("shouldn't be able to transfer eth (wrong password).", async () => { + const res_ = await simplePost("/auth/sign-in", { + password: user.password, + userName: user.userName, + }); + + const { res } = await postWithToken( + "/auth/sign-in", + "/web3/transfer-to", + { password: user2.password, userName: user2.userName }, + { + password: "1256", + to: res_.body.user.account.address, + value: "0.001", + } + ); + + expect(res.status).toBe(401); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual(errors.wrongWalletPassword.message); + expect(res.body.error.code).toEqual(errors.wrongWalletPassword.code); + }); + test("shouldn't be able to transfer eth (no wallet).", async () => { + const res_ = await simplePost("/auth/sign-in", { + password: user.password, + userName: user.userName, + }); + + const { res, res1 } = await postWithToken( + "/auth/sign-in", + "/web3/transfer-to", + { password: user3.password, userName: user3.userName }, + { + password: "1234", + to: res_.body.user.account.address, + value: "0.001", + } + ); + + expect(res.status).toBe(400); + expect(res.body.ok).toEqual(false); + expect(res.body.error.message).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).message + ); + expect(res.body.error.code).toEqual( + errors.invalidIDorNoWallet(res1.body.user.id).code + ); + }); +}); diff --git a/cattleia-backend/src/test/test.http b/cattleia-backend/src/test/test.http deleted file mode 100644 index 90051dd..0000000 --- a/cattleia-backend/src/test/test.http +++ /dev/null @@ -1,68 +0,0 @@ -POST http://localhost:4000/api/v1/auth/sign-up HTTP/1.1 -Content-Type: application/json - -{ - "name":"akumi2", - "email":"aku2@gmail.com", - "userName":"aku2", - "password":"test2" -} - -### - -POST http://localhost:4000/api/v1/auth/sign-in HTTP/1.1 -Content-Type: application/json - -{ - "userName":"aku2", - "password":"test2" -} - -### - -POST http://localhost:4000/api/v1/web3/create-account HTTP/1.1 -Content-Type: application/json -Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWt1MiIsIl9pZCI6IjYxMmJiNDRjOTk2OGU5MWVhODIyYjI5YyIsInJvbGUiOiJ1c2VyIiwidG9rZW5WZXJzaW9uIjowLCJpYXQiOjE2MzAyNTQxNTksImV4cCI6MTYzMDM0MDU1OX0.PGNkNRtpblQ_JtrtEJVkNU21a1ViwKPHi_qNZb7bi5g - -{ - "password":"test2" -} - -### - -POST http://localhost:4000/api/v1/web3/get-key HTTP/1.1 -Content-Type: application/json -Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWt1MiIsIl9pZCI6IjYxMmJiNDRjOTk2OGU5MWVhODIyYjI5YyIsInJvbGUiOiJ1c2VyIiwidG9rZW5WZXJzaW9uIjowLCJpYXQiOjE2MzAyNTQxNTksImV4cCI6MTYzMDM0MDU1OX0.PGNkNRtpblQ_JtrtEJVkNU21a1ViwKPHi_qNZb7bi5g - -{ - "password":"test2" -} - -### - -GET http://localhost:4000/api/v1/web3/balance HTTP/1.1 -Content-Type: application/json -Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWt1IiwiX2lkIjoiNjEyYmE5OWY3MjY0M2ExZTE4YjRjYmQ1Iiwicm9sZSI6InVzZXIiLCJ0b2tlblZlcnNpb24iOjAsImlhdCI6MTYzMDI1NDU2OCwiZXhwIjoxNjMwMzQwOTY4fQ._fQXzPQvilyfZ2uXpf-8jwtc71sJPwMrq0Cm4shV3iY - -### - -POST http://localhost:4000/api/v1/web3/import-account HTTP/1.1 -Content-Type: application/json -Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWt1MiIsIl9pZCI6IjYxMmVjZjU2MjYzNGE5MDVmNGJmODcxYyIsInJvbGUiOiJ1c2VyIiwidG9rZW5WZXJzaW9uIjowLCJpYXQiOjE2MzA0NTc2OTcsImV4cCI6MTYzMDU0NDA5N30.8Ut_GEg75J5bJVa2PN4SyEeVXCOb1uk2MQlTiy7rgZU - -{ - "privateKey":"2587479acdce25311aee18357b73a01187e070707251f2763a64daa7b8a155a5", - "password":"test2" -} - -### - -POST http://localhost:4000/api/v1/web3/transfer-to HTTP/1.1 -Content-Type: application/json -Authorization: bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWt1MiIsIl9pZCI6IjYxMmJiNDRjOTk2OGU5MWVhODIyYjI5YyIsInJvbGUiOiJ1c2VyIiwidG9rZW5WZXJzaW9uIjowLCJpYXQiOjE2MzAyNTQxNTksImV4cCI6MTYzMDM0MDU1OX0.PGNkNRtpblQ_JtrtEJVkNU21a1ViwKPHi_qNZb7bi5g - -{ - "password":"test2", - "to":"0xc59dc2664c12fa760d3b2f16c49ceef5b96dd2f7", - "value":"1" -} \ No newline at end of file diff --git a/cattleia-backend/types.d.ts b/cattleia-backend/types.d.ts index e2f95c6..9f8f4ff 100644 --- a/cattleia-backend/types.d.ts +++ b/cattleia-backend/types.d.ts @@ -21,6 +21,7 @@ declare namespace NodeJS { STRIPE_SECRET: string; STRIPE_PUBLISHABLE: string; NODE_ENV: "production" | "development" | "test"; + TEST_PRIVATE_KEY: string; } }