Filter everything with users (#16)
* Filter everything with users * Fix backtests and user management * Add cursor rules * Fix backtest and bots * Update configs names * Sign until unauth * Setup delegate * Setup delegate and sign * refact * Enhance Privy signature generation with improved cryptographic methods * Add Fastify backend * Add Fastify backend routes for privy * fix privy signing * fix privy client * Fix tests * add gmx core * fix merging sdk * Fix tests * add gmx core * add gmx core * add privy to boilerplate * clean * fix * add fastify * Remove Managing.Fastify submodule * Add Managing.Fastify as regular directory instead of submodule * Update .gitignore to exclude Managing.Fastify dist and node_modules directories * Add token approval functionality to Privy plugin - Introduced a new endpoint `/approve-token` for approving ERC20 tokens. - Added `approveToken` method to the Privy plugin for handling token approvals. - Updated `signPrivyMessage` to differentiate between message signing and token approval requests. - Enhanced the plugin with additional schemas for input validation. - Included new utility functions for token data retrieval and message construction. - Updated tests to verify the new functionality and ensure proper request decoration. * Add PrivyApproveTokenResponse model for token approval response - Created a new class `PrivyApproveTokenResponse` to encapsulate the response structure for token approval requests. - The class includes properties for `Success` status and a transaction `Hash`. * Refactor trading commands and enhance API routes - Updated `OpenPositionCommandHandler` to use asynchronous methods for opening trades and canceling orders. - Introduced new Fastify routes for opening positions and canceling orders with appropriate request validation. - Modified `EvmManager` to handle both Privy and non-Privy wallet operations, utilizing the Fastify API for Privy wallets. - Adjusted test configurations to reflect changes in account types and added helper methods for testing Web3 proxy services. * Enhance GMX trading functionality and update dependencies - Updated `dev:start` script in `package.json` to include the `-d` flag for Fastify. - Upgraded `fastify-cli` dependency to version 7.3.0. - Added `sourceMap` option to `tsconfig.json`. - Refactored GMX plugin to improve position opening logic, including enhanced error handling and validation. - Introduced a new method `getMarketInfoFromTicker` for better market data retrieval. - Updated account type in `PrivateKeys.cs` to use `Privy`. - Adjusted `EvmManager` to utilize the `direction` enum directly for trade direction handling. * Refactor GMX plugin for improved trading logic and market data retrieval - Enhanced the `openGmxPositionImpl` function to utilize the `TradeDirection` enum for trade direction handling. - Introduced `getTokenDataFromTicker` and `getMarketByIndexToken` functions for better market and token data retrieval. - Updated collateral calculation and logging for clarity. - Adjusted `EvmManager` to ensure proper handling of price values in trade requests. * Refactor GMX plugin and enhance testing for position opening - Updated `test:single` script in `package.json` to include TypeScript compilation before running tests. - Removed `this` context from `getClientForAddress` function and replaced logging with `console.error`. - Improved collateral calculation in `openGmxPositionImpl` for better precision. - Adjusted type casting for `direction` in the API route to utilize `TradeDirection` enum. - Added a new test for opening a long position in GMX, ensuring functionality and correctness. * Update sdk * Update * update fastify * Refactor start script in package.json to simplify command execution - Removed the build step from the start script, allowing for a more direct launch of the Fastify server. * Update package.json for Web3Proxy - Changed the name from "Web3Proxy" to "web3-proxy". - Updated version from "0.0.0" to "1.0.0". - Modified the description to "The official Managing Web3 Proxy". * Update Dockerfile for Web3Proxy - Upgraded Node.js base image from 18-alpine to 22.14.0-alpine. - Added NODE_ENV environment variable set to production. * Refactor Dockerfile and package.json for Web3Proxy - Removed the build step from the Dockerfile to streamline the image creation process. - Updated the start script in package.json to include the build step, ensuring the application is built before starting the server. * Add fastify-tsconfig as a development dependency in Dockerfile-web3proxy * Remove fastify-tsconfig extension from tsconfig.json for Web3Proxy * Add PrivyInitAddressResponse model for handling initialization responses - Introduced a new class `PrivyInitAddressResponse` to encapsulate the response structure for Privy initialization, including properties for success status, USDC hash, order vault hash, and error message. * Update * Update * Remove fastify-tsconfig installation from Dockerfile-web3proxy * Add build step to Dockerfile-web3proxy - Included `npm run build` in the Dockerfile to ensure the application is built during the image creation process. * Update * approvals * Open position from front embedded wallet * Open position from front embedded wallet * Open position from front embedded wallet * Fix call contracts * Fix limit price * Close position * Fix close position * Fix close position * add pinky * Refactor position handling logic * Update Dockerfile-pinky to copy package.json and source code from the correct directory * Implement password protection modal and enhance UI with new styles; remove unused audio elements and update package dependencies. * add cancel orders * Update callContract function to explicitly cast account address as Address type * Update callContract function to cast transaction parameters as any type for compatibility * Cast transaction parameters as any type in approveTokenImpl for compatibility * Cast wallet address and transaction parameters as Address type in approveTokenImpl for type safety * Add .env configuration file for production setup including database and server settings * Refactor home route to update welcome message and remove unused SDK configuration code * add referral code * fix referral * Add sltp * Fix typo * Fix typo * setup sltp on backtend * get orders * get positions with slp * fixes * fixes close position * fixes * Remove MongoDB project references from Dockerfiles for managing and worker APIs * Comment out BotManagerWorker service registration and remove MongoDB project reference from Dockerfile * fixes
This commit is contained in:
26
src/Managing.Web3Proxy/test/routes/api/api.test.ts
Normal file
26
src/Managing.Web3Proxy/test/routes/api/api.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { build } from '../../helper.js'
|
||||
|
||||
test('GET /api with no login', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.inject({
|
||||
url: '/api'
|
||||
})
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), {
|
||||
message: 'You must be authenticated to access this route.'
|
||||
})
|
||||
})
|
||||
|
||||
test('GET /api with cookie', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
url: '/api'
|
||||
})
|
||||
|
||||
assert.equal(res.statusCode, 200)
|
||||
assert.ok(JSON.parse(res.payload).message.startsWith('Hello basic!'))
|
||||
})
|
||||
117
src/Managing.Web3Proxy/test/routes/api/auth/auth.test.ts
Normal file
117
src/Managing.Web3Proxy/test/routes/api/auth/auth.test.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { describe, it } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { build, expectValidationError } from '../../../helper.js'
|
||||
|
||||
describe('Auth api', () => {
|
||||
describe('POST /api/auth/login', () => {
|
||||
it('Transaction should rollback on error', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const { mock: mockCompare } = t.mock.method(app, 'compare')
|
||||
mockCompare.mockImplementationOnce((value: string, hash: string) => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
const { mock: mockLogError } = t.mock.method(app.log, 'error')
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: {
|
||||
email: 'basic@example.com',
|
||||
password: 'Password123$'
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(mockCompare.callCount(), 1)
|
||||
|
||||
const arg = mockLogError.calls[0].arguments[0] as unknown as {
|
||||
err: Error;
|
||||
}
|
||||
|
||||
assert.strictEqual(res.statusCode, 500)
|
||||
assert.deepStrictEqual(arg.err.message, 'Transaction failed.')
|
||||
})
|
||||
|
||||
it('should return 400 if credentials payload is invalid', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const invalidPayload = {
|
||||
email: '',
|
||||
password: 'Password123$'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: invalidPayload
|
||||
})
|
||||
|
||||
expectValidationError(
|
||||
res,
|
||||
'body/email must NOT have fewer than 1 characters'
|
||||
)
|
||||
})
|
||||
|
||||
it('should authenticate with valid credentials', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: {
|
||||
email: 'basic@example.com',
|
||||
password: 'Password123$'
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
assert.ok(
|
||||
res.cookies.some((cookie) => cookie.name === app.config.COOKIE_NAME)
|
||||
)
|
||||
})
|
||||
|
||||
it('should not authneticate with invalid credentials', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const testCases = [
|
||||
{
|
||||
email: 'invalid@email.com',
|
||||
password: 'password',
|
||||
description: 'invalid email'
|
||||
},
|
||||
{
|
||||
email: 'basic@example.com',
|
||||
password: 'wrong_password',
|
||||
description: 'invalid password'
|
||||
},
|
||||
{
|
||||
email: 'invalid@email.com',
|
||||
password: 'wrong_password',
|
||||
description: 'both invalid'
|
||||
}
|
||||
]
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: {
|
||||
email: testCase.email,
|
||||
password: testCase.password
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(
|
||||
res.statusCode,
|
||||
401,
|
||||
`Failed for case: ${testCase.description}`
|
||||
)
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), {
|
||||
message: 'Invalid email or password.'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,2 @@
|
||||
Line
|
||||
This is a very small CSV with one line.
|
||||
|
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
861
src/Managing.Web3Proxy/test/routes/api/tasks/tasks.test.ts
Normal file
861
src/Managing.Web3Proxy/test/routes/api/tasks/tasks.test.ts
Normal file
@@ -0,0 +1,861 @@
|
||||
import { after, before, beforeEach, describe, it } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { build, expectValidationError } from '../../../helper.js'
|
||||
import {
|
||||
Task,
|
||||
TaskStatusEnum,
|
||||
TaskPaginationResultSchema
|
||||
} from '../../../../src/schemas/tasks.js'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { Static } from '@sinclair/typebox'
|
||||
import fs from 'node:fs'
|
||||
import { pipeline } from 'node:stream/promises'
|
||||
import path from 'node:path'
|
||||
import FormData from 'form-data'
|
||||
import os from 'node:os'
|
||||
import { gunzipSync } from 'node:zlib'
|
||||
|
||||
async function createUser (
|
||||
app: FastifyInstance,
|
||||
userData: Partial<{ email: string; username: string; password: string }>
|
||||
) {
|
||||
const [id] = await app.knex('users').insert(userData)
|
||||
return id
|
||||
}
|
||||
|
||||
async function createTask (app: FastifyInstance, taskData: Partial<Task>) {
|
||||
const [id] = await app.knex<Task>('tasks').insert(taskData)
|
||||
|
||||
return id
|
||||
}
|
||||
|
||||
async function uploadImageForTask (
|
||||
app: FastifyInstance,
|
||||
taskId: number,
|
||||
filePath: string,
|
||||
uploadDir: string
|
||||
) {
|
||||
await app
|
||||
.knex<Task>('tasks')
|
||||
.where({ id: taskId })
|
||||
.update({ filename: `${taskId}_short-logo.png` })
|
||||
|
||||
const file = fs.createReadStream(filePath)
|
||||
const filename = `${taskId}_short-logo.png`
|
||||
|
||||
const writeStream = fs.createWriteStream(path.join(uploadDir, filename))
|
||||
|
||||
await pipeline(file, writeStream)
|
||||
}
|
||||
|
||||
describe('Tasks api (logged user only)', () => {
|
||||
describe('GET /api/tasks', () => {
|
||||
let app: FastifyInstance
|
||||
let userId1: number
|
||||
let userId2: number
|
||||
|
||||
let firstTaskId: number
|
||||
|
||||
before(async () => {
|
||||
app = await build()
|
||||
|
||||
userId1 = await createUser(app, {
|
||||
username: 'user1',
|
||||
email: 'user1@example.com',
|
||||
password: 'password1'
|
||||
})
|
||||
userId2 = await createUser(app, {
|
||||
username: 'user2',
|
||||
email: 'user2@example.com',
|
||||
password: 'password2'
|
||||
})
|
||||
|
||||
firstTaskId = await createTask(app, {
|
||||
name: 'Task 1',
|
||||
author_id: userId1,
|
||||
status: TaskStatusEnum.New
|
||||
})
|
||||
await createTask(app, {
|
||||
name: 'Task 2',
|
||||
author_id: userId1,
|
||||
assigned_user_id: userId2,
|
||||
status: TaskStatusEnum.InProgress
|
||||
})
|
||||
await createTask(app, {
|
||||
name: 'Task 3',
|
||||
author_id: userId2,
|
||||
status: TaskStatusEnum.Completed
|
||||
})
|
||||
await createTask(app, {
|
||||
name: 'Task 4',
|
||||
author_id: userId1,
|
||||
assigned_user_id: userId1,
|
||||
status: TaskStatusEnum.OnHold
|
||||
})
|
||||
|
||||
app.close()
|
||||
})
|
||||
|
||||
it('should return a list of tasks with no pagination filter', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
const firstTask = tasks.find((task) => task.id === firstTaskId)
|
||||
|
||||
assert.ok(firstTask, 'Created task should be in the response')
|
||||
assert.deepStrictEqual(firstTask.name, 'Task 1')
|
||||
assert.strictEqual(firstTask.author_id, userId1)
|
||||
assert.strictEqual(firstTask.status, TaskStatusEnum.New)
|
||||
|
||||
assert.strictEqual(total, 4)
|
||||
})
|
||||
|
||||
it('should paginate by page and limit', async (t) => {
|
||||
app = await build(t)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks',
|
||||
query: { page: '2', limit: '1' }
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
|
||||
assert.strictEqual(total, 4)
|
||||
assert.strictEqual(tasks.length, 1)
|
||||
assert.strictEqual(tasks[0].name, 'Task 2')
|
||||
assert.strictEqual(tasks[0].author_id, userId1)
|
||||
assert.strictEqual(tasks[0].status, TaskStatusEnum.InProgress)
|
||||
})
|
||||
|
||||
it('should filter tasks by assigned_user_id', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks',
|
||||
query: { assigned_user_id: userId2.toString() }
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
|
||||
assert.strictEqual(total, 1)
|
||||
tasks.forEach((task) =>
|
||||
assert.strictEqual(task.assigned_user_id, userId2)
|
||||
)
|
||||
})
|
||||
|
||||
it('should filter tasks by status', async (t) => {
|
||||
app = await build(t)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks',
|
||||
query: { status: TaskStatusEnum.Completed }
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
|
||||
assert.strictEqual(total, 1)
|
||||
tasks.forEach((task) =>
|
||||
assert.strictEqual(task.status, TaskStatusEnum.Completed)
|
||||
)
|
||||
})
|
||||
|
||||
it('should paginate and filter tasks by author_id and status', async (t) => {
|
||||
app = await build(t)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks',
|
||||
query: {
|
||||
author_id: userId1.toString(),
|
||||
status: TaskStatusEnum.OnHold,
|
||||
page: '1',
|
||||
limit: '1'
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
|
||||
assert.strictEqual(total, 1)
|
||||
assert.strictEqual(tasks.length, 1)
|
||||
assert.strictEqual(tasks[0].name, 'Task 4')
|
||||
assert.strictEqual(tasks[0].author_id, userId1)
|
||||
assert.strictEqual(tasks[0].status, TaskStatusEnum.OnHold)
|
||||
})
|
||||
|
||||
it('should return empty array and total = 0 if no tasks', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
await app.knex<Task>('tasks').delete()
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const { tasks, total } = JSON.parse(res.payload) as Static<
|
||||
typeof TaskPaginationResultSchema
|
||||
>
|
||||
|
||||
assert.strictEqual(total, 0)
|
||||
assert.strictEqual(tasks.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/tasks/:id', () => {
|
||||
it('should return a task', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const taskData = {
|
||||
name: 'Single Task',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New
|
||||
}
|
||||
|
||||
const newTaskId = await createTask(app, taskData)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: `/api/tasks/${newTaskId}`
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const task = JSON.parse(res.payload) as Task
|
||||
assert.equal(task.id, newTaskId)
|
||||
})
|
||||
|
||||
it('should return 404 if task is not found', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks/9999'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const payload = JSON.parse(res.payload)
|
||||
assert.strictEqual(payload.message, 'Task not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/tasks', () => {
|
||||
it('should return 400 if task creation payload is invalid', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const invalidTaskData = {
|
||||
name: ''
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks',
|
||||
payload: invalidTaskData
|
||||
})
|
||||
|
||||
expectValidationError(res, 'body/name must NOT have fewer than 1 characters')
|
||||
})
|
||||
|
||||
it('should create a new task', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const taskData = {
|
||||
name: 'New Task'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks',
|
||||
payload: taskData
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 201)
|
||||
const { id } = JSON.parse(res.payload)
|
||||
|
||||
const createdTask = await app.knex<Task>('tasks').where({ id }).first()
|
||||
assert.equal(createdTask?.name, taskData.name)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PATCH /api/tasks/:id', () => {
|
||||
it('should return 400 if task update payload is invalid', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const invalidUpdateData = {
|
||||
name: 'Updated task',
|
||||
assigned_user_id: 'abc'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'PATCH',
|
||||
url: '/api/tasks/1',
|
||||
payload: invalidUpdateData
|
||||
})
|
||||
|
||||
expectValidationError(res, 'body/assigned_user_id must be integer')
|
||||
})
|
||||
|
||||
it('should update an existing task', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const taskData = {
|
||||
name: 'Task to Update',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New
|
||||
}
|
||||
const newTaskId = await createTask(app, taskData)
|
||||
|
||||
const updatedData = {
|
||||
name: 'Updated Task'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'PATCH',
|
||||
url: `/api/tasks/${newTaskId}`,
|
||||
payload: updatedData
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
const updatedTask = await app
|
||||
.knex<Task>('tasks')
|
||||
.where({ id: newTaskId })
|
||||
.first()
|
||||
assert.equal(updatedTask?.name, updatedData.name)
|
||||
})
|
||||
|
||||
it('should return 404 if task is not found for update', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const updatedData = {
|
||||
name: 'Updated Task'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'PATCH',
|
||||
url: '/api/tasks/9999',
|
||||
payload: updatedData
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const payload = JSON.parse(res.payload)
|
||||
assert.strictEqual(payload.message, 'Task not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE /api/tasks/:id', () => {
|
||||
const taskData = {
|
||||
name: 'Task to Delete',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New
|
||||
}
|
||||
|
||||
it('should delete an existing task', async (t) => {
|
||||
const app = await build(t)
|
||||
const newTaskId = await createTask(app, taskData)
|
||||
|
||||
const res = await app.injectWithLogin('admin@example.com', {
|
||||
method: 'DELETE',
|
||||
url: `/api/tasks/${newTaskId}`
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 204)
|
||||
|
||||
const deletedTask = await app
|
||||
.knex<Task>('tasks')
|
||||
.where({ id: newTaskId })
|
||||
.first()
|
||||
assert.strictEqual(deletedTask, undefined)
|
||||
})
|
||||
|
||||
it('should return 404 if task is not found for deletion', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('admin@example.com', {
|
||||
method: 'DELETE',
|
||||
url: '/api/tasks/9999'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const payload = JSON.parse(res.payload)
|
||||
assert.strictEqual(payload.message, 'Task not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('POST /api/tasks/:id/assign', () => {
|
||||
it('should return 400 if task assignment payload is invalid', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const invalidPayload = {
|
||||
userId: 'not-a-number'
|
||||
}
|
||||
|
||||
const res = await app.injectWithLogin('moderator@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks/1/assign',
|
||||
payload: invalidPayload
|
||||
})
|
||||
|
||||
expectValidationError(res, 'body/userId must be number')
|
||||
})
|
||||
|
||||
it('should assign a task to a user and persist the changes', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
for (const email of ['moderator@example.com', 'admin@example.com']) {
|
||||
const taskData = {
|
||||
name: 'Task to Assign',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New
|
||||
}
|
||||
const newTaskId = await createTask(app, taskData)
|
||||
|
||||
const res = await app.injectWithLogin(email, {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${newTaskId}/assign`,
|
||||
payload: {
|
||||
userId: 2
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
|
||||
const updatedTask = await app
|
||||
.knex<Task>('tasks')
|
||||
.where({ id: newTaskId })
|
||||
.first()
|
||||
assert.strictEqual(updatedTask?.assigned_user_id, 2)
|
||||
}
|
||||
})
|
||||
|
||||
it('should unassign a task from a user and persist the changes', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
for (const email of ['moderator@example.com', 'admin@example.com']) {
|
||||
const taskData = {
|
||||
name: 'Task to Unassign',
|
||||
author_id: 1,
|
||||
assigned_user_id: 2,
|
||||
status: TaskStatusEnum.New
|
||||
}
|
||||
const newTaskId = await createTask(app, taskData)
|
||||
|
||||
const res = await app.injectWithLogin(email, {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${newTaskId}/assign`,
|
||||
payload: {}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
|
||||
const updatedTask = await app
|
||||
.knex<Task>('tasks')
|
||||
.where({ id: newTaskId })
|
||||
.first()
|
||||
assert.strictEqual(updatedTask?.assigned_user_id, null)
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 403 if not a moderator', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks/1/assign',
|
||||
payload: {}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 403)
|
||||
})
|
||||
|
||||
it('should return 404 if task is not found', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('moderator@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks/9999/assign',
|
||||
payload: {
|
||||
userId: 2
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const payload = JSON.parse(res.payload)
|
||||
assert.strictEqual(payload.message, 'Task not found')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Task image upload, retrieval and delete', () => {
|
||||
let app: FastifyInstance
|
||||
let taskId: number
|
||||
const filename = 'short-logo.png'
|
||||
const fixturesDir = path.join(import.meta.dirname, './fixtures')
|
||||
const testImagePath = path.join(fixturesDir, filename)
|
||||
const testCsvPath = path.join(fixturesDir, 'one_line.csv')
|
||||
let uploadDir: string
|
||||
let uploadDirTask: string
|
||||
|
||||
before(async () => {
|
||||
app = await build()
|
||||
uploadDir = path.join(import.meta.dirname, '../../../../', app.config.UPLOAD_DIRNAME)
|
||||
uploadDirTask = path.join(uploadDir, app.config.UPLOAD_TASKS_DIRNAME)
|
||||
assert.ok(fs.existsSync(uploadDir))
|
||||
|
||||
taskId = await createTask(app, {
|
||||
name: 'Task with image',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New
|
||||
})
|
||||
|
||||
app.close()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
const files = fs.readdirSync(uploadDirTask)
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(uploadDirTask, file)
|
||||
fs.rmSync(filePath, { recursive: true })
|
||||
})
|
||||
|
||||
await app.close()
|
||||
})
|
||||
|
||||
describe('Upload', () => {
|
||||
it('should create upload directories at boot if not exist', async (t) => {
|
||||
fs.rmSync(uploadDir, { recursive: true })
|
||||
assert.ok(!fs.existsSync(uploadDir))
|
||||
|
||||
app = await build(t)
|
||||
|
||||
assert.ok(fs.existsSync(uploadDir))
|
||||
assert.ok(fs.existsSync(uploadDirTask))
|
||||
})
|
||||
|
||||
it('should upload a valid image for a task', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(testImagePath))
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${taskId}/upload`,
|
||||
payload: form,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
|
||||
const { message } = JSON.parse(res.payload)
|
||||
assert.strictEqual(message, 'File uploaded successfully')
|
||||
})
|
||||
|
||||
it('should return 404 if task not found', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(testImagePath))
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/tasks/100000/upload',
|
||||
payload: form,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
|
||||
const { message } = JSON.parse(res.payload)
|
||||
assert.strictEqual(message, 'Task not found')
|
||||
})
|
||||
|
||||
it('should return 404 if file not found', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(testImagePath))
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${taskId}/upload`,
|
||||
payload: undefined,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
|
||||
const { message } = JSON.parse(res.payload)
|
||||
assert.strictEqual(message, 'File not found')
|
||||
})
|
||||
|
||||
it('should reject an invalid file type', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(testCsvPath))
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${taskId}/upload`,
|
||||
payload: form,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
expectValidationError(res, 'Invalid file type')
|
||||
})
|
||||
|
||||
it('should reject if file size exceeds limit (truncated)', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const tmpDir = os.tmpdir()
|
||||
const largeTestImagePath = path.join(tmpDir, 'large-test-image.jpg')
|
||||
|
||||
const largeBuffer = Buffer.alloc(1024 * 1024 * 1.5, 'a') // Max file size in bytes is 1 MB
|
||||
fs.writeFileSync(largeTestImagePath, largeBuffer)
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(largeTestImagePath))
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${taskId}/upload`,
|
||||
payload: form,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
expectValidationError(res, 'File size limit exceeded')
|
||||
})
|
||||
|
||||
it('File upload transaction should rollback on error', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const { mock: mockPipeline } = t.mock.method(fs, 'createWriteStream')
|
||||
mockPipeline.mockImplementationOnce(() => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
const { mock: mockLogError } = t.mock.method(app.log, 'error')
|
||||
|
||||
const form = new FormData()
|
||||
form.append('file', fs.createReadStream(testImagePath))
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'POST',
|
||||
url: `/api/tasks/${taskId}/upload`,
|
||||
payload: form,
|
||||
headers: form.getHeaders()
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 500)
|
||||
assert.strictEqual(mockLogError.callCount(), 1)
|
||||
|
||||
const arg = mockLogError.calls[0].arguments[0] as unknown as {
|
||||
err: Error;
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(arg.err.message, 'Transaction failed.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Retrieval', () => {
|
||||
it('should retrieve the uploaded image based on task id and filename', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const taskFilename = encodeURIComponent(`${taskId}_${filename}`)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: `/api/tasks/${taskFilename}/image`
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
assert.strictEqual(res.headers['content-type'], 'image/png')
|
||||
|
||||
const originalFile = fs.readFileSync(testImagePath)
|
||||
|
||||
assert.deepStrictEqual(originalFile, res.rawPayload)
|
||||
})
|
||||
|
||||
it('should return 404 error for non-existant filename', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks/non-existant/image'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const { message } = JSON.parse(res.payload)
|
||||
assert.strictEqual(message, 'No task has filename "non-existant"')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Deletion', () => {
|
||||
before(async () => {
|
||||
app = await build()
|
||||
|
||||
await app.knex<Task>('tasks').where({ id: taskId }).update({ filename: null })
|
||||
|
||||
const files = fs.readdirSync(uploadDirTask)
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(uploadDirTask, file)
|
||||
fs.rmSync(filePath, { recursive: true })
|
||||
})
|
||||
|
||||
await app.close()
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await build()
|
||||
await uploadImageForTask(app, taskId, testImagePath, uploadDirTask)
|
||||
await app.close()
|
||||
})
|
||||
|
||||
after(async () => {
|
||||
const files = fs.readdirSync(uploadDirTask)
|
||||
files.forEach((file) => {
|
||||
const filePath = path.join(uploadDirTask, file)
|
||||
fs.rmSync(filePath, { recursive: true })
|
||||
})
|
||||
})
|
||||
|
||||
it('should remove an existing image for a task', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const taskFilename = encodeURIComponent(`${taskId}_${filename}`)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'DELETE',
|
||||
url: `/api/tasks/${taskFilename}/image`
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 204)
|
||||
|
||||
const files = fs.readdirSync(uploadDirTask)
|
||||
assert.strictEqual(files.length, 0)
|
||||
})
|
||||
|
||||
it('should return 404 for non-existant task with filename for deletion', async (t) => {
|
||||
app = await build(t)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'DELETE',
|
||||
url: '/api/tasks/non-existant/image'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 404)
|
||||
const { message } = JSON.parse(res.payload)
|
||||
assert.strictEqual(message, 'No task has filename "non-existant"')
|
||||
})
|
||||
|
||||
it('should return 204 for non-existant file in upload dir', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
await createTask(app, {
|
||||
name: 'Task with image',
|
||||
author_id: 1,
|
||||
status: TaskStatusEnum.New,
|
||||
filename: 'does_not_exist.png'
|
||||
})
|
||||
|
||||
const { mock: mockLogWarn } = t.mock.method(app.log, 'warn')
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'DELETE',
|
||||
url: '/api/tasks/does_not_exist.png/image'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 204)
|
||||
|
||||
const arg = mockLogWarn.calls[0].arguments[0]
|
||||
|
||||
assert.strictEqual(mockLogWarn.callCount(), 1)
|
||||
assert.deepStrictEqual(arg, 'File path \'does_not_exist.png\' not found')
|
||||
})
|
||||
|
||||
it('File deletion transaction should rollback on error', async (t) => {
|
||||
const app = await build(t)
|
||||
const { mock: mockPipeline } = t.mock.method(fs.promises, 'unlink')
|
||||
mockPipeline.mockImplementationOnce(() => {
|
||||
return Promise.reject(new Error())
|
||||
})
|
||||
|
||||
const { mock: mockLogError } = t.mock.method(app.log, 'error')
|
||||
|
||||
const taskFilename = encodeURIComponent(`${taskId}_${filename}`)
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'DELETE',
|
||||
url: `/api/tasks/${taskFilename}/image`
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 500)
|
||||
assert.strictEqual(mockLogError.callCount(), 1)
|
||||
|
||||
const arg = mockLogError.calls[0].arguments[0] as unknown as {
|
||||
err: Error;
|
||||
}
|
||||
|
||||
assert.deepStrictEqual(arg.err.message, 'Transaction failed.')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /api/tasks/download/csv', () => {
|
||||
before(async () => {
|
||||
const app = await build()
|
||||
await app.knex('tasks').del()
|
||||
await app.close()
|
||||
})
|
||||
|
||||
it('should stream a gzipped CSV file', async (t) => {
|
||||
const app = await build(t)
|
||||
|
||||
const tasks = []
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
tasks.push({
|
||||
name: `Task ${i + 1}`,
|
||||
author_id: 1,
|
||||
assigned_user_id: 2,
|
||||
filename: 'task.png',
|
||||
status: TaskStatusEnum.InProgress
|
||||
})
|
||||
}
|
||||
|
||||
await app.knex('tasks').insert(tasks)
|
||||
|
||||
const res = await app.injectWithLogin('basic@example.com', {
|
||||
method: 'GET',
|
||||
url: '/api/tasks/download/csv'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
assert.strictEqual(res.headers['content-type'], 'application/gzip')
|
||||
assert.strictEqual(
|
||||
res.headers['content-disposition'],
|
||||
'attachment; filename="tasks.csv.gz"'
|
||||
)
|
||||
|
||||
const decompressed = gunzipSync(res.rawPayload).toString('utf-8')
|
||||
const lines = decompressed.split('\n')
|
||||
assert.equal(lines.length - 1, 1001)
|
||||
|
||||
assert.ok(lines[1].includes('Task 1,1,2,task.png,in-progress'))
|
||||
assert.equal(lines[0], 'id,name,author_id,assigned_user_id,filename,status,created_at,updated_at')
|
||||
})
|
||||
})
|
||||
})
|
||||
192
src/Managing.Web3Proxy/test/routes/api/users/users.test.ts
Normal file
192
src/Managing.Web3Proxy/test/routes/api/users/users.test.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { it, describe, beforeEach, afterEach } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { build } from '../../../helper.js'
|
||||
import { FastifyInstance } from 'fastify'
|
||||
import { scryptHash } from '../../../../src/plugins/custom/scrypt.js'
|
||||
|
||||
async function createUser (app: FastifyInstance, userData: Partial<{ username: string; email: string; password: string }>) {
|
||||
const [id] = await app.knex('users').insert(userData)
|
||||
return id
|
||||
}
|
||||
|
||||
async function deleteUser (app: FastifyInstance, username: string) {
|
||||
await app.knex('users').delete().where({ username })
|
||||
}
|
||||
|
||||
async function updatePasswordWithLoginInjection (app: FastifyInstance, username: string, payload: { currentPassword: string; newPassword: string }) {
|
||||
return app.injectWithLogin(`${username}@example.com`, {
|
||||
method: 'PUT',
|
||||
url: '/api/users/update-password',
|
||||
payload
|
||||
})
|
||||
}
|
||||
|
||||
describe('Users API', async () => {
|
||||
const hash = await scryptHash('Password123$')
|
||||
let app: FastifyInstance
|
||||
|
||||
beforeEach(async () => {
|
||||
app = await build()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close()
|
||||
})
|
||||
|
||||
it('Should enforce rate limiting by returning a 429 status after exceeding 3 password update attempts within 1 minute', async () => {
|
||||
await createUser(app, { username: 'random-user-0', email: 'random-user-0@example.com', password: hash })
|
||||
|
||||
const loginResponse = await app.injectWithLogin('random-user-0@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: {
|
||||
email: 'random-user-0@example.com',
|
||||
password: 'Password123$'
|
||||
}
|
||||
})
|
||||
|
||||
app.config = {
|
||||
...app.config,
|
||||
COOKIE_SECRET: loginResponse.cookies[0].value
|
||||
}
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const resInner = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/users/update-password',
|
||||
payload: {
|
||||
currentPassword: 'Password1234$',
|
||||
newPassword: 'Password123$'
|
||||
},
|
||||
cookies: {
|
||||
[app.config.COOKIE_NAME]: loginResponse.cookies[0].value
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(resInner.statusCode, 401)
|
||||
}
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/users/update-password',
|
||||
payload: {
|
||||
currentPassword: 'Password1234$',
|
||||
newPassword: 'Password123$'
|
||||
},
|
||||
cookies: {
|
||||
[app.config.COOKIE_NAME]: loginResponse.cookies[0].value
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 429)
|
||||
await deleteUser(app, 'random-user-0')
|
||||
})
|
||||
|
||||
it('Should update the password successfully', async () => {
|
||||
await createUser(app, { username: 'random-user-1', email: 'random-user-1@example.com', password: hash })
|
||||
const res = await updatePasswordWithLoginInjection(app, 'random-user-1', {
|
||||
currentPassword: 'Password123$',
|
||||
newPassword: 'NewPassword123$'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 200)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'Password updated successfully' })
|
||||
|
||||
await deleteUser(app, 'random-user-1')
|
||||
})
|
||||
|
||||
it('Should return 400 if the new password is the same as current password', async () => {
|
||||
await createUser(app, { username: 'random-user-2', email: 'random-user-2@example.com', password: hash })
|
||||
const res = await updatePasswordWithLoginInjection(app, 'random-user-2', {
|
||||
currentPassword: 'Password123$',
|
||||
newPassword: 'Password123$'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 400)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'New password cannot be the same as the current password.' })
|
||||
|
||||
await deleteUser(app, 'random-user-2')
|
||||
})
|
||||
|
||||
it('Should return 400 if the newPassword password not match the required pattern', async () => {
|
||||
await createUser(app, { username: 'random-user-3', email: 'random-user-3@example.com', password: hash })
|
||||
const res = await updatePasswordWithLoginInjection(app, 'random-user-3', {
|
||||
currentPassword: 'Password123$',
|
||||
newPassword: 'password123$'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 400)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'body/newPassword must match pattern "^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).*$"' })
|
||||
|
||||
await deleteUser(app, 'random-user-3')
|
||||
})
|
||||
|
||||
it('Should return 401 the current password is incorrect', async () => {
|
||||
await createUser(app, { username: 'random-user-4', email: 'random-user-4@example.com', password: hash })
|
||||
const res = await updatePasswordWithLoginInjection(app, 'random-user-4', {
|
||||
currentPassword: 'WrongPassword123$',
|
||||
newPassword: 'Password123$'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 401)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'Invalid current password.' })
|
||||
|
||||
await deleteUser(app, 'random-user-4')
|
||||
})
|
||||
|
||||
it('Should return 401 if user does not exist in the database', async () => {
|
||||
await createUser(app, { username: 'random-user-5', email: 'random-user-5@example.com', password: hash })
|
||||
const loginResponse = await app.injectWithLogin('random-user-5@example.com', {
|
||||
method: 'POST',
|
||||
url: '/api/auth/login',
|
||||
payload: {
|
||||
email: 'random-user-5@example.com',
|
||||
password: 'Password123$'
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(loginResponse.statusCode, 200)
|
||||
|
||||
await deleteUser(app, 'random-user-5')
|
||||
|
||||
app.config = {
|
||||
...app.config,
|
||||
COOKIE_SECRET: loginResponse.cookies[0].value
|
||||
}
|
||||
|
||||
const res = await app.inject({
|
||||
method: 'PUT',
|
||||
url: '/api/users/update-password',
|
||||
payload: {
|
||||
currentPassword: 'Password123$',
|
||||
newPassword: 'NewPassword123$'
|
||||
},
|
||||
cookies: {
|
||||
[app.config.COOKIE_NAME]: loginResponse.cookies[0].value
|
||||
}
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 401)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'User does not exist.' })
|
||||
await deleteUser(app, 'random-user-5')
|
||||
})
|
||||
|
||||
it('Should handle errors gracefully and return 500 Internal Server Error when an unexpected error occurs', async (t) => {
|
||||
const { mock: mockKnex } = t.mock.method(app, 'hash')
|
||||
mockKnex.mockImplementation(() => {
|
||||
throw new Error()
|
||||
})
|
||||
|
||||
await createUser(app, { username: 'random-user-6', email: 'random-user-6@example.com', password: hash })
|
||||
|
||||
const res = await updatePasswordWithLoginInjection(app, 'random-user-6', {
|
||||
currentPassword: 'Password123$',
|
||||
newPassword: 'NewPassword123$'
|
||||
})
|
||||
|
||||
assert.strictEqual(res.statusCode, 500)
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), { message: 'Internal Server Error' })
|
||||
|
||||
await deleteUser(app, 'random-user-6')
|
||||
})
|
||||
})
|
||||
14
src/Managing.Web3Proxy/test/routes/home.test.ts
Normal file
14
src/Managing.Web3Proxy/test/routes/home.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { test } from 'node:test'
|
||||
import assert from 'node:assert'
|
||||
import { build } from '../helper.js'
|
||||
|
||||
test('GET /', async (t) => {
|
||||
const app = await build(t)
|
||||
const res = await app.inject({
|
||||
url: '/'
|
||||
})
|
||||
|
||||
assert.deepStrictEqual(JSON.parse(res.payload), {
|
||||
message: 'Welcome to the official fastify demo!'
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user