From cb95de09e881dbf2f1571df4481978a00f5506ce Mon Sep 17 00:00:00 2001 From: PCBZ Date: Wed, 23 Apr 2025 00:02:26 -0700 Subject: [PATCH 1/7] update client test --- client/package-lock.json | 42 +++++++++++--------- client/package.json | 2 +- client/src/App.test.jsx | 84 +++++++++++++++++++++++++++++++++------- 3 files changed, 94 insertions(+), 34 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 68c220dd..4a25c1ee 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,6 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", - "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", @@ -30,6 +29,7 @@ "web-vitals": "^2.1.4" }, "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "eslint": "^8.56.0", @@ -41,6 +41,7 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.2.tgz", "integrity": "sha512-baYZExFpsdkBNuvGKTKWCwKH57HRZLVtycZS05WTQNVOiXVSeAki3nU35zlRbToeMW8aHlJfyS+1C4BOv27q0A==", + "dev": true, "license": "MIT" }, "node_modules/@alloc/quick-lru": { @@ -3902,23 +3903,22 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", - "integrity": "sha512-ynmNeT7asXyH3aSVv4vvX4Rb+0qjOhdNHnO/3vuZNqPmhDpV/+rCSGwQ7bLcmU2cJ4dvoheIO85LQj0IbJHEtg==", + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.6.3.tgz", + "integrity": "sha512-IteBhl4XqYNkM54f4ejhLRJiZNqcSCoXUOG2CPK7qbD322KjQozM4kHQOfkG2oln9b9HTYqs+Sae8vBATubxxA==", + "dev": true, "license": "MIT", "dependencies": { - "@adobe/css-tools": "^4.0.1", - "@babel/runtime": "^7.9.2", - "@types/testing-library__jest-dom": "^5.9.1", + "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "chalk": "^3.0.0", "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.5.6", - "lodash": "^4.17.15", + "dom-accessibility-api": "^0.6.3", + "lodash": "^4.17.21", "redent": "^3.0.0" }, "engines": { - "node": ">=8", + "node": ">=14", "npm": ">=6", "yarn": ">=1" } @@ -3927,6 +3927,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3936,6 +3937,13 @@ "node": ">=8" } }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, "node_modules/@testing-library/react": { "version": "13.4.0", "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-13.4.0.tgz", @@ -4362,15 +4370,6 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, - "node_modules/@types/testing-library__jest-dom": { - "version": "5.14.9", - "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", - "integrity": "sha512-FSYhIjFlfOpGSRyVoMBMuS3ws5ehFQODymf3vlI7U1K8c7PHwWwFY7VREfmsuzHSOnoKs/9/Y983ayOs7eRzqw==", - "license": "MIT", - "dependencies": { - "@types/jest": "*" - } - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -6702,6 +6701,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, "license": "MIT" }, "node_modules/cssdb": { @@ -9826,6 +9826,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -12008,6 +12009,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -14749,6 +14751,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, "license": "MIT", "dependencies": { "indent-string": "^4.0.0", @@ -16253,6 +16256,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, "license": "MIT", "dependencies": { "min-indent": "^1.0.0" diff --git a/client/package.json b/client/package.json index fb2323fc..2c19259b 100644 --- a/client/package.json +++ b/client/package.json @@ -8,7 +8,6 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.10", "@mui/material": "^5.15.10", - "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", @@ -54,6 +53,7 @@ ] }, "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "eslint": "^8.56.0", diff --git a/client/src/App.test.jsx b/client/src/App.test.jsx index 67e68968..e3aa0716 100644 --- a/client/src/App.test.jsx +++ b/client/src/App.test.jsx @@ -1,28 +1,84 @@ import React from 'react'; import { render, screen } from '@testing-library/react'; -import { BrowserRouter } from 'react-router-dom'; +import { MemoryRouter } from 'react-router-dom'; import '@testing-library/jest-dom'; import App from './App'; +import { isAuthenticated, getCurrentUser } from './services/authService'; -const TestApp = () => ( - - - -); +// Mock the auth service +jest.mock('./services/authService', () => ({ + isAuthenticated: jest.fn(), + getCurrentUser: jest.fn().mockReturnValue({ + id: 'test-user-id', + username: 'testuser', + email: 'test@example.com' + }) +})); + +// Mock the Router component +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + BrowserRouter: ({ children }) =>
{children}
, + HashRouter: ({ children }) =>
{children}
, +})); describe('App Component', () => { - it('renders without crashing', () => { - render(); + beforeEach(() => { + // Reset all mocks before each test + jest.clearAllMocks(); + }); + + test('renders without crashing', () => { + render( + + + + ); }); - it('redirects to login page by default', () => { - render(); - expect(window.location.pathname).toBe('/login'); + test('redirects to login when not authenticated', () => { + isAuthenticated.mockReturnValue(false); + render( + + + + ); + + expect(isAuthenticated).toHaveBeenCalled(); }); - it('renders Navbar component', () => { - render(); - const navbar = screen.getByTestId('navbar'); + test('redirects to dashboard when authenticated', () => { + isAuthenticated.mockReturnValue(true); + render( + + + + ); + + expect(isAuthenticated).toHaveBeenCalled(); + }); + + test('renders Navbar component', () => { + render( + + + + ); + + // Assuming Navbar has some identifiable text or element + const navbar = screen.getByRole('navigation'); expect(navbar).toBeInTheDocument(); }); + + test('displays current user information', () => { + isAuthenticated.mockReturnValue(true); + render( + + + + ); + + expect(getCurrentUser).toHaveBeenCalled(); + // Add assertions for user information display if applicable + }); }); \ No newline at end of file From dd84143e36dba56d91522a986ef0d8688758fccd Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 00:41:47 -0700 Subject: [PATCH 2/7] set up server test --- Server/jest.config.js | 2 +- Server/test/setup.js | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 Server/test/setup.js diff --git a/Server/jest.config.js b/Server/jest.config.js index 15dc70f5..dc1bb8fa 100644 --- a/Server/jest.config.js +++ b/Server/jest.config.js @@ -1,4 +1,4 @@ -module.exports = { +export default { testEnvironment: 'node', setupFilesAfterEnv: ['./test/setup.js'], testMatch: ['**/test/**/*.test.js'], diff --git a/Server/test/setup.js b/Server/test/setup.js new file mode 100644 index 00000000..16b93cf0 --- /dev/null +++ b/Server/test/setup.js @@ -0,0 +1,7 @@ +// 测试环境的全局设置 +import { jest } from '@jest/globals'; + +// 设置全局超时时间 +jest.setTimeout(10000); + +// 可以在这里添加其他全局测试设置 \ No newline at end of file From 03ca4d6114702b44c41ec2e3e76947813c309c98 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 12:25:41 -0700 Subject: [PATCH 3/7] set up ci test database --- .github/workflows/ci.yml | 55 ++++++++++++++++++++++++++++++++++------ Server/test/setup.js | 22 ++++++++++++++++ 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 557c2c5e..7a4c867a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,6 +35,42 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm run install:all + + - name: Build client + working-directory: ./client + env: + CI: false + run: npm run build + + test: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:8.0 + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: testdb + MYSQL_USER: testuser + MYSQL_PASSWORD: testpass + ports: + - 3306:3306 + options: >- + --health-cmd="mysqladmin ping" + --health-interval=10s + --health-timeout=5s + --health-retries=3 + + steps: + - uses: actions/checkout@v3 + - name: Setup Node.js uses: actions/setup-node@v3 with: @@ -47,15 +83,18 @@ jobs: - name: Run client tests working-directory: ./client run: npm test -- --watchAll=false - continue-on-error: true - - name: Run server tests + - name: Set up test database working-directory: ./Server - run: npm test - continue-on-error: true + env: + DATABASE_URL: "mysql://testuser:testpass@localhost:3306/testdb" + run: | + npx prisma generate + npx prisma db push - - name: Build client - working-directory: ./client + - name: Run server tests + working-directory: ./Server env: - CI: false - run: npm run build \ No newline at end of file + DATABASE_URL: "mysql://testuser:testpass@localhost:3306/testdb" + NODE_ENV: test + run: npm test \ No newline at end of file diff --git a/Server/test/setup.js b/Server/test/setup.js index 16b93cf0..a4ea9596 100644 --- a/Server/test/setup.js +++ b/Server/test/setup.js @@ -1,7 +1,29 @@ // 测试环境的全局设置 import { jest } from '@jest/globals'; +import { PrismaClient } from '@prisma/client'; // 设置全局超时时间 jest.setTimeout(10000); +// 创建测试数据库连接 +const prisma = new PrismaClient(); + +// 在测试开始前清理数据库 +beforeAll(async () => { + // 删除所有测试数据 + await prisma.eventDonor.deleteMany(); + await prisma.eventDonorList.deleteMany(); + await prisma.event.deleteMany(); + await prisma.donor.deleteMany(); + await prisma.user.deleteMany(); +}); + +// 在测试结束后关闭数据库连接 +afterAll(async () => { + await prisma.$disconnect(); +}); + +// 导出 prisma 实例供测试使用 +export { prisma }; + // 可以在这里添加其他全局测试设置 \ No newline at end of file From fb52c9412905516ccc58468b9d8a70cd072c3cb1 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 15:36:06 -0700 Subject: [PATCH 4/7] change user server test --- Server/test/user/getUser.test.js | 15 ++++++++++++--- Server/test/user/logout.test.js | 20 ++++++++++++++++---- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/Server/test/user/getUser.test.js b/Server/test/user/getUser.test.js index 2b3e5343..11ca608f 100644 --- a/Server/test/user/getUser.test.js +++ b/Server/test/user/getUser.test.js @@ -93,7 +93,10 @@ describe('Get User Details', () => { .get(`/api/user/${userId}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }, 10000); it('should not get user details with invalid token', async () => { @@ -102,7 +105,10 @@ describe('Get User Details', () => { .set('Authorization', 'Bearer invalid_token'); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, token failed'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_003'); + expect(response.body.error).toHaveProperty('message', 'Invalid token'); }, 10000); it('should not get non-existent user', async () => { @@ -136,6 +142,9 @@ describe('Get User Details', () => { .set('Authorization', `Bearer ${expiredToken}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, token failed'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_003'); + expect(response.body.error).toHaveProperty('message', 'Invalid token'); }, 10000); }); \ No newline at end of file diff --git a/Server/test/user/logout.test.js b/Server/test/user/logout.test.js index 74469c2a..3e530dc4 100644 --- a/Server/test/user/logout.test.js +++ b/Server/test/user/logout.test.js @@ -103,7 +103,10 @@ describe('User Logout', () => { .post('/api/user/logout'); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }, 10000); it('should not logout with invalid token', async () => { @@ -112,7 +115,10 @@ describe('User Logout', () => { .set('Authorization', 'Bearer invalid_token'); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, token failed'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_003'); + expect(response.body.error).toHaveProperty('message', 'Invalid token'); }, 10000); it('should not logout with malformed token', async () => { @@ -121,7 +127,10 @@ describe('User Logout', () => { .set('Authorization', 'InvalidTokenFormat'); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }, 10000); it('should not logout with expired token', async () => { @@ -137,6 +146,9 @@ describe('User Logout', () => { .set('Authorization', `Bearer ${expiredToken}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, token failed'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_003'); + expect(response.body.error).toHaveProperty('message', 'Invalid token'); }, 10000); }); \ No newline at end of file From 2e9a2675f24a35631492bd110735f11feda69d5a Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 18:05:08 -0700 Subject: [PATCH 5/7] update donor test --- Server/test/donor/getDonor.test.js | 52 +++++++++++++++++--- Server/test/donorList/addDonors.test.js | 5 +- Server/test/donorList/deleteList.test.js | 5 +- Server/test/donorList/getListDetails.test.js | 5 +- Server/test/donorList/getLists.test.js | 5 +- Server/test/donorList/removeDonor.test.js | 5 +- Server/test/donorList/updateStatus.test.js | 5 +- 7 files changed, 70 insertions(+), 12 deletions(-) diff --git a/Server/test/donor/getDonor.test.js b/Server/test/donor/getDonor.test.js index 5fcd5d01..6af93590 100644 --- a/Server/test/donor/getDonor.test.js +++ b/Server/test/donor/getDonor.test.js @@ -8,6 +8,42 @@ const prisma = new PrismaClient(); // Increase timeout for database operations jest.setTimeout(30000); +// Mock authentication middleware +jest.mock('../../src/middleware/auth.js', () => ({ + protect: jest.fn((req, res, next) => { + if (!req.headers.authorization) { + return res.status(401).json({ + success: false, + error: { + code: 'AUTH_001', + message: 'Authentication token is missing', + details: 'Please provide a valid Bearer token in the Authorization header' + } + }); + } + + const token = req.headers.authorization.split(' ')[1]; + if (token !== 'valid_test_token') { + return res.status(401).json({ + success: false, + error: { + code: 'AUTH_003', + message: 'Invalid token', + details: 'The provided token is invalid or has expired' + } + }); + } + + req.user = { + id: 1, + name: 'Test User', + email: 'test.user@example.com', + role: 'pmm' + }; + next(); + }) +})); + // Test user data for authentication const testUser = { id: 1, @@ -16,13 +52,9 @@ const testUser = { role: 'pmm' }; -// Generate a valid token for testing +// 简化token生成,直接返回预定义的有效token const generateToken = () => { - return jwt.sign( - { userId: testUser.id, role: testUser.role }, - process.env.JWT_SECRET || 'your_jwt_secret_key', - { expiresIn: '1h' } - ); + return 'valid_test_token'; }; // Test donor data @@ -124,6 +156,10 @@ describe('Get Donor API', () => { .get(`/api/donors/${donorId}`); expect(response.status).toBe(401); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }, 10000); }); @@ -200,6 +236,10 @@ describe('Get Donor API', () => { .get('/api/donors'); expect(response.status).toBe(401); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }, 10000); }); }); \ No newline at end of file diff --git a/Server/test/donorList/addDonors.test.js b/Server/test/donorList/addDonors.test.js index d84f54cd..d9d003ec 100644 --- a/Server/test/donorList/addDonors.test.js +++ b/Server/test/donorList/addDonors.test.js @@ -250,7 +250,10 @@ describe('Add Donors to List API Tests', () => { }); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); it('should handle multiple donors in a single request', async () => { diff --git a/Server/test/donorList/deleteList.test.js b/Server/test/donorList/deleteList.test.js index 85621cac..80e969f8 100644 --- a/Server/test/donorList/deleteList.test.js +++ b/Server/test/donorList/deleteList.test.js @@ -212,6 +212,9 @@ describe('Delete Donor List API Tests', () => { .delete(`/api/lists/${testList.id}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); }); \ No newline at end of file diff --git a/Server/test/donorList/getListDetails.test.js b/Server/test/donorList/getListDetails.test.js index cf2c246d..74f3e720 100644 --- a/Server/test/donorList/getListDetails.test.js +++ b/Server/test/donorList/getListDetails.test.js @@ -233,6 +233,9 @@ describe('Get Donor List Details API Tests', () => { .get(`/api/lists/${testList.id}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); }); \ No newline at end of file diff --git a/Server/test/donorList/getLists.test.js b/Server/test/donorList/getLists.test.js index 4f4b3071..48b82b07 100644 --- a/Server/test/donorList/getLists.test.js +++ b/Server/test/donorList/getLists.test.js @@ -164,6 +164,9 @@ describe('Get All Donor Lists API Tests', () => { .get('/api/lists'); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); }); \ No newline at end of file diff --git a/Server/test/donorList/removeDonor.test.js b/Server/test/donorList/removeDonor.test.js index 03bf799b..360384ab 100644 --- a/Server/test/donorList/removeDonor.test.js +++ b/Server/test/donorList/removeDonor.test.js @@ -279,6 +279,9 @@ describe('Remove Donor from List API Tests', () => { .delete(`/api/lists/${testList.id}/donors/${testDonor.id}`); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); }); \ No newline at end of file diff --git a/Server/test/donorList/updateStatus.test.js b/Server/test/donorList/updateStatus.test.js index 3ef9c74f..25f9c2eb 100644 --- a/Server/test/donorList/updateStatus.test.js +++ b/Server/test/donorList/updateStatus.test.js @@ -219,6 +219,9 @@ describe('Update List Status API Tests', () => { .send({ review_status: 'completed' }); expect(response.status).toBe(401); - expect(response.body).toHaveProperty('message', 'Not authorized, no token'); + expect(response.body).toHaveProperty('success', false); + expect(response.body).toHaveProperty('error'); + expect(response.body.error).toHaveProperty('code', 'AUTH_001'); + expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); }); \ No newline at end of file From 7819aa538cea8f8cde07859d6a99b03d91b8e55b Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 18:15:02 -0700 Subject: [PATCH 6/7] fix add donors test --- Server/test/donorList/addDonors.test.js | 48 ++++++-------------- Server/test/donorList/deleteList.test.js | 4 +- Server/test/donorList/getListDetails.test.js | 4 +- Server/test/donorList/getLists.test.js | 4 +- Server/test/donorList/removeDonor.test.js | 4 +- Server/test/donorList/updateStatus.test.js | 5 +- 6 files changed, 24 insertions(+), 45 deletions(-) diff --git a/Server/test/donorList/addDonors.test.js b/Server/test/donorList/addDonors.test.js index d9d003ec..b5093b11 100644 --- a/Server/test/donorList/addDonors.test.js +++ b/Server/test/donorList/addDonors.test.js @@ -201,18 +201,14 @@ describe('Add Donors to List API Tests', () => { .post(`/api/lists/${testList.id}/donors`) .set('Authorization', `Bearer ${authToken}`) .send({ - donors: [{ - donor_id: testDonor.id.toString(), - status: 'Pending', - comments: 'Test donor' - }] + donorIds: [testDonor.id.toString()] }); - expect(response.status).toBe(201); - expect(response.body).toHaveProperty('message', 'Donors added successfully.'); - expect(response.body).toHaveProperty('added_donors'); - expect(response.body.added_donors).toBeInstanceOf(Array); - expect(response.body.added_donors.length).toBe(1); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('message'); + expect(response.body).toHaveProperty('added', 1); + expect(response.body).toHaveProperty('totalDonors', 1); + expect(response.body).toHaveProperty('pending', 1); // Verify list statistics have been updated const updatedList = await prisma.eventDonorList.findUnique({ @@ -227,31 +223,22 @@ describe('Add Donors to List API Tests', () => { .post('/api/lists/999999/donors') .set('Authorization', `Bearer ${authToken}`) .send({ - donors: [{ - donor_id: testDonor.id.toString(), - status: 'Pending', - comments: 'Test donor' - }] + donorIds: [testDonor.id.toString()] }); expect(response.status).toBe(404); - expect(response.body).toHaveProperty('message', 'List not found'); + expect(response.body).toHaveProperty('message', 'Donor list not found'); }); it('should return 401 without authentication', async () => { const response = await request(app) .post(`/api/lists/${testList.id}/donors`) .send({ - donors: [{ - donor_id: testDonor.id.toString(), - status: 'Pending', - comments: 'Test donor' - }] + donorIds: [testDonor.id.toString()] }); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); @@ -271,22 +258,13 @@ describe('Add Donors to List API Tests', () => { .post(`/api/lists/${testList.id}/donors`) .set('Authorization', `Bearer ${authToken}`) .send({ - donors: [ - { - donor_id: testDonor.id.toString(), - status: 'Pending', - comments: 'First donor' - }, - { - donor_id: secondDonor.id.toString(), - status: 'Pending', - comments: 'Second donor' - } + donorIds: [ + secondDonor.id.toString() ] }); - expect(response.status).toBe(201); - expect(response.body.added_donors).toHaveLength(2); + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('added', 1); // Clean up second test donor // First delete the event donor record diff --git a/Server/test/donorList/deleteList.test.js b/Server/test/donorList/deleteList.test.js index 80e969f8..42af2142 100644 --- a/Server/test/donorList/deleteList.test.js +++ b/Server/test/donorList/deleteList.test.js @@ -209,11 +209,11 @@ describe('Delete Donor List API Tests', () => { it('should return 401 without authentication', async () => { const response = await request(app) - .delete(`/api/lists/${testList.id}`); + .delete(`/api/lists/${testList.id}`) + .send(); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); diff --git a/Server/test/donorList/getListDetails.test.js b/Server/test/donorList/getListDetails.test.js index 74f3e720..7b698f44 100644 --- a/Server/test/donorList/getListDetails.test.js +++ b/Server/test/donorList/getListDetails.test.js @@ -230,11 +230,11 @@ describe('Get Donor List Details API Tests', () => { it('should return 401 without authentication', async () => { const response = await request(app) - .get(`/api/lists/${testList.id}`); + .get(`/api/lists/${testList.id}`) + .send(); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); diff --git a/Server/test/donorList/getLists.test.js b/Server/test/donorList/getLists.test.js index 48b82b07..905c0431 100644 --- a/Server/test/donorList/getLists.test.js +++ b/Server/test/donorList/getLists.test.js @@ -161,11 +161,11 @@ describe('Get All Donor Lists API Tests', () => { it('should return 401 without authentication', async () => { const response = await request(app) - .get('/api/lists'); + .get('/api/lists') + .send(); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); diff --git a/Server/test/donorList/removeDonor.test.js b/Server/test/donorList/removeDonor.test.js index 360384ab..df272251 100644 --- a/Server/test/donorList/removeDonor.test.js +++ b/Server/test/donorList/removeDonor.test.js @@ -276,11 +276,11 @@ describe('Remove Donor from List API Tests', () => { it('should return 401 without authentication', async () => { const response = await request(app) - .delete(`/api/lists/${testList.id}/donors/${testDonor.id}`); + .delete(`/api/lists/${testList.id}/donors/${testDonor.id}`) + .send(); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); diff --git a/Server/test/donorList/updateStatus.test.js b/Server/test/donorList/updateStatus.test.js index 25f9c2eb..b708eb39 100644 --- a/Server/test/donorList/updateStatus.test.js +++ b/Server/test/donorList/updateStatus.test.js @@ -216,11 +216,12 @@ describe('Update List Status API Tests', () => { it('should return 401 without authentication', async () => { const response = await request(app) .put(`/api/lists/${testList.id}/status`) - .send({ review_status: 'completed' }); + .send({ + review_status: 'completed' + }); expect(response.status).toBe(401); expect(response.body).toHaveProperty('success', false); - expect(response.body).toHaveProperty('error'); expect(response.body.error).toHaveProperty('code', 'AUTH_001'); expect(response.body.error).toHaveProperty('message', 'Authentication token is missing'); }); From a69e3bd2692c8593782dd8188db71de4ba86b553 Mon Sep 17 00:00:00 2001 From: PCBZ Date: Thu, 24 Apr 2025 20:17:26 -0700 Subject: [PATCH 7/7] update event test --- Server/test/donor/importDonor.test.js | 157 -------------------------- Server/test/event/deleteEvent.test.js | 12 +- Server/test/helpers/testSetup.js | 22 ++-- 3 files changed, 22 insertions(+), 169 deletions(-) delete mode 100644 Server/test/donor/importDonor.test.js diff --git a/Server/test/donor/importDonor.test.js b/Server/test/donor/importDonor.test.js deleted file mode 100644 index 0ca9f33b..00000000 --- a/Server/test/donor/importDonor.test.js +++ /dev/null @@ -1,157 +0,0 @@ -import request from 'supertest'; -import { PrismaClient } from '@prisma/client'; -import fs from 'fs'; -import path from 'path'; -import app from '../../src/index.js'; -import progressService from '../../src/routes/progressService.js'; - -const testCsvPath = path.join(__dirname, '..', 'fixtures', 'test-donors.csv'); -const prisma = new PrismaClient(); - - -// Increase timeout for database operations -jest.setTimeout(30000); - - -// Mock authentication middleware -jest.mock('../../src/middleware/auth.js', () => ({ - protect: jest.fn((req, res, next) => { - req.user = { id: 1, role: 'pmm' }; - next(); - }) -})); - -describe('Donor Import Tests', () => { - beforeAll(async () => { - try { - await prisma.$connect(); - - // 清理 progressService 中的操作和定时器 - progressService.operations.clear(); - if (progressService.cleanupInterval) { - clearInterval(progressService.cleanupInterval); - progressService.cleanupInterval = null; - } - - // Verify the test CSV file exists - if (!fs.existsSync(testCsvPath)) { - console.warn(`Test CSV file not found at ${testCsvPath}. Some tests may fail.`); - } - - // Clean up any existing test data with recognizable first and last names - await prisma.donor.deleteMany({ - where: { - OR: [ - { firstName: { equals: 'Mei' } }, - { firstName: { equals: 'Olga' } }, - { firstName: { equals: 'Sergei' } } - ] - } - }); - } catch (error) { - console.error('Setup error:', error); - throw error; - } - }); - - afterAll(async () => { - try { - // Clean up test data - await prisma.donor.deleteMany({ - where: { - OR: [ - { firstName: { equals: 'Mei' } }, - { firstName: { equals: 'Olga' } }, - { firstName: { equals: 'Sergei' } } - ] - } - }); - - // 清理 progressService 中的操作和定时器 - progressService.operations.clear(); - if (progressService.cleanupInterval) { - clearInterval(progressService.cleanupInterval); - progressService.cleanupInterval = null; - } - } catch (error) { - console.error('Cleanup error:', error); - } finally { - await prisma.$disconnect(); - } - }); - - // 在每个测试后清理 progressService - afterEach(() => { - progressService.operations.clear(); - if (progressService.cleanupInterval) { - clearInterval(progressService.cleanupInterval); - progressService.cleanupInterval = null; - } - }); - - it('should import donors from CSV file', async () => { - // Skip the test if the CSV file doesn't exist - if (!fs.existsSync(testCsvPath)) { - console.warn('Skipping test: CSV file not found'); - return; - } - - // Log CSV content to verify it's being read correctly - const csvContent = fs.readFileSync(testCsvPath, 'utf8'); - console.log('CSV first 200 chars:', csvContent.substring(0, 200)); - - // Use the existing CSV file - const response = await request(app) - .post('/api/donors/import') - .attach('file', testCsvPath); - - console.log('Import response:', JSON.stringify(response.body, null, 2)); - - expect(response.status).toBe(200); - expect(response.body).toHaveProperty('success', true); - expect(response.body).toHaveProperty('imported'); - expect(response.body.imported).toBeGreaterThanOrEqual(1); - - // Check what donors actually exist in the database after import - const allDonors = await prisma.donor.findMany({ - take: 5 // Limit to first 5 results for logging - }); - - console.log('Donors in database after import:', JSON.stringify(allDonors, null, 2)); - - // Check specifically for the Mei Lee donor with more flexible criteria - const importedDonor = await prisma.donor.findFirst({ - where: { - OR: [ - // Try different field name variations - { firstName: 'Mei' } ] - } - }); - - console.log('Found donor:', importedDonor); - - expect(importedDonor).toBeTruthy(); - - // If we found the donor, verify its properties - if (importedDonor) { - // Try different field name patterns (camelCase vs snake_case) - const pmm = importedDonor.pmm || importedDonor.Pmm; - const city = importedDonor.city || importedDonor.City; - const nickName = importedDonor.nickName || importedDonor.nick_name; - - expect(pmm).toBe('Parvati Patel'); - expect(city).toBe('North Vancouver'); - expect(nickName).toBe('Sunshine'); - } - }); - - describe('Error handling during import', () => { - it('should return 400 if no file is uploaded', async () => { - const response = await request(app) - .post('/api/donors/import'); - - expect(response.status).toBe(400); - expect(response.body).toHaveProperty('message', 'No file uploaded'); - }); - }); -}); \ No newline at end of file diff --git a/Server/test/event/deleteEvent.test.js b/Server/test/event/deleteEvent.test.js index 20ce3458..526794d6 100644 --- a/Server/test/event/deleteEvent.test.js +++ b/Server/test/event/deleteEvent.test.js @@ -78,12 +78,13 @@ describe('DELETE /api/events/:id', () => { expect(response.status).toBe(200); expect(response.body).toHaveProperty('message', 'Event deleted successfully'); - // Verify the event was actually deleted from the database + // Verify the event was soft deleted (isDeleted flag set to true) const deletedEvent = await prisma.event.findUnique({ where: { id: parseInt(testEventId) } }); - expect(deletedEvent).toBeNull(); + expect(deletedEvent).not.toBeNull(); + expect(deletedEvent.isDeleted).toBe(true); }); it('should handle invalid event ID format', async () => { @@ -95,7 +96,7 @@ describe('DELETE /api/events/:id', () => { expect(response.body).toHaveProperty('message', 'Invalid event ID format'); }); - it('should return 404 for non-existent event ID', async () => { + it('should allow deleting an already deleted event', async () => { // First delete the test event await request(app) .delete(`/api/events/${testEventId}`) @@ -106,8 +107,9 @@ describe('DELETE /api/events/:id', () => { .delete(`/api/events/${testEventId}`) .set('Authorization', `Bearer ${authToken}`); - expect(response.status).toBe(404); - expect(response.body).toHaveProperty('message', 'Event not found'); + // Should still return success + expect(response.status).toBe(200); + expect(response.body).toHaveProperty('message', 'Event deleted successfully'); }); it('should require authentication', async () => { diff --git a/Server/test/helpers/testSetup.js b/Server/test/helpers/testSetup.js index 2219c488..ee6b26f4 100644 --- a/Server/test/helpers/testSetup.js +++ b/Server/test/helpers/testSetup.js @@ -51,14 +51,22 @@ export const setupTestUser = async (prisma, userData) => { * @param {object} prisma - PrismaClient instance */ export const cleanupTestEvents = async (prisma) => { - await prisma.event.deleteMany({ - where: { - name: { - contains: 'Test' + try { + // Use soft delete instead of actual delete to avoid foreign key constraint errors + await prisma.event.updateMany({ + where: { + name: { + contains: 'Test' + } + }, + data: { + isDeleted: true } - } - }); - console.log('Cleaned up test events'); + }); + console.log('Cleaned up test events'); + } catch (error) { + console.error('Error during test event cleanup:', error); + } }; /**