Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ad9bb16c2 | ||
| 510787bc5b | |||
|
|
9f696e7676 | ||
|
|
a77105316f | ||
| cadacb63f5 | |||
|
|
62592f707e | ||
| 023e48d99a |
@@ -185,7 +185,17 @@ jobs:
|
||||
- name: Show PM2 Environment for Production
|
||||
run: |
|
||||
echo "--- Displaying recent PM2 logs for flyer-crawler-api ---"
|
||||
sleep 5
|
||||
pm2 describe flyer-crawler-api || echo "Could not find production pm2 process."
|
||||
pm2 logs flyer-crawler-api --lines 20 --nostream || echo "Could not find production pm2 process."
|
||||
pm2 env flyer-crawler-api || echo "Could not find production pm2 process."
|
||||
sleep 5 # Wait a few seconds for the app to start and log its output.
|
||||
|
||||
# Resolve the PM2 ID dynamically to ensure we target the correct process
|
||||
PM2_ID=$(pm2 jlist | node -e "try { const list = JSON.parse(require('fs').readFileSync(0, 'utf-8')); const app = list.find(p => p.name === 'flyer-crawler-api'); console.log(app ? app.pm2_env.pm_id : ''); } catch(e) { console.log(''); }")
|
||||
|
||||
if [ -n "$PM2_ID" ]; then
|
||||
echo "Found process ID: $PM2_ID"
|
||||
pm2 describe "$PM2_ID" || echo "Failed to describe process $PM2_ID"
|
||||
pm2 logs "$PM2_ID" --lines 20 --nostream || echo "Failed to get logs for $PM2_ID"
|
||||
pm2 env "$PM2_ID" || echo "Failed to get env for $PM2_ID"
|
||||
else
|
||||
echo "Could not find process 'flyer-crawler-api' in pm2 list."
|
||||
pm2 list # Fallback to listing everything to help debug
|
||||
fi
|
||||
|
||||
@@ -461,7 +461,17 @@ jobs:
|
||||
run: |
|
||||
echo "--- Displaying recent PM2 logs for flyer-crawler-api-test ---"
|
||||
# After a reload, the server restarts. We'll show the last 20 lines of the log to see the startup messages.
|
||||
sleep 5 # Wait a few seconds for the app to start and log its output.
|
||||
pm2 describe flyer-crawler-api-test || echo "Could not find test pm2 process."
|
||||
pm2 logs flyer-crawler-api-test --lines 20 --nostream || echo "Could not find test pm2 process."
|
||||
pm2 env flyer-crawler-api-test || echo "Could not find test pm2 process."
|
||||
sleep 5
|
||||
|
||||
# Resolve the PM2 ID dynamically to ensure we target the correct process
|
||||
PM2_ID=$(pm2 jlist | node -e "try { const list = JSON.parse(require('fs').readFileSync(0, 'utf-8')); const app = list.find(p => p.name === 'flyer-crawler-api-test'); console.log(app ? app.pm2_env.pm_id : ''); } catch(e) { console.log(''); }")
|
||||
|
||||
if [ -n "$PM2_ID" ]; then
|
||||
echo "Found process ID: $PM2_ID"
|
||||
pm2 describe "$PM2_ID" || echo "Failed to describe process $PM2_ID"
|
||||
pm2 logs "$PM2_ID" --lines 20 --nostream || echo "Failed to get logs for $PM2_ID"
|
||||
pm2 env "$PM2_ID" || echo "Failed to get env for $PM2_ID"
|
||||
else
|
||||
echo "Could not find process 'flyer-crawler-api-test' in pm2 list."
|
||||
pm2 list # Fallback to listing everything to help debug
|
||||
fi
|
||||
|
||||
@@ -21,6 +21,7 @@ module.exports = {
|
||||
{
|
||||
// --- API Server ---
|
||||
name: 'flyer-crawler-api',
|
||||
// Note: The process names below are referenced in .gitea/workflows/ for status checks.
|
||||
script: './node_modules/.bin/tsx',
|
||||
args: 'server.ts',
|
||||
max_memory_restart: '500M',
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.4.4",
|
||||
"version": "0.5.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.4.4",
|
||||
"version": "0.5.1",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.4.4",
|
||||
"version": "0.5.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -26,6 +26,7 @@ export function useApi<T, TArgs extends unknown[]>(
|
||||
const [isRefetching, setIsRefetching] = useState<boolean>(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
const hasBeenExecuted = useRef(false);
|
||||
const lastErrorMessageRef = useRef<string | null>(null);
|
||||
const abortControllerRef = useRef<AbortController>(new AbortController());
|
||||
|
||||
// This effect ensures that when the component using the hook unmounts,
|
||||
@@ -52,6 +53,7 @@ export function useApi<T, TArgs extends unknown[]>(
|
||||
async (...args: TArgs): Promise<T | null> => {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
lastErrorMessageRef.current = null;
|
||||
if (hasBeenExecuted.current) {
|
||||
setIsRefetching(true);
|
||||
}
|
||||
@@ -106,7 +108,13 @@ export function useApi<T, TArgs extends unknown[]>(
|
||||
error: err.message,
|
||||
functionName: apiFunction.name,
|
||||
});
|
||||
setError(err);
|
||||
// Only set a new error object if the message is different from the last one.
|
||||
// This prevents creating new object references for the same error (e.g. repeated timeouts)
|
||||
// and helps break infinite loops in components that depend on the `error` object.
|
||||
if (err.message !== lastErrorMessageRef.current) {
|
||||
setError(err);
|
||||
lastErrorMessageRef.current = err.message;
|
||||
}
|
||||
notifyError(err.message); // Optionally notify the user automatically.
|
||||
return null; // Return null on failure.
|
||||
} finally {
|
||||
|
||||
@@ -47,6 +47,7 @@ export function useInfiniteQuery<T>(
|
||||
|
||||
// Use a ref to store the cursor for the next page.
|
||||
const nextCursorRef = useRef<number | string | null | undefined>(initialCursor);
|
||||
const lastErrorMessageRef = useRef<string | null>(null);
|
||||
|
||||
const fetchPage = useCallback(
|
||||
async (cursor?: number | string | null) => {
|
||||
@@ -59,6 +60,7 @@ export function useInfiniteQuery<T>(
|
||||
setIsFetchingNextPage(true);
|
||||
}
|
||||
setError(null);
|
||||
lastErrorMessageRef.current = null;
|
||||
|
||||
try {
|
||||
const response = await apiFunction(cursor);
|
||||
@@ -99,7 +101,10 @@ export function useInfiniteQuery<T>(
|
||||
error: err.message,
|
||||
functionName: apiFunction.name,
|
||||
});
|
||||
setError(err);
|
||||
if (err.message !== lastErrorMessageRef.current) {
|
||||
setError(err);
|
||||
lastErrorMessageRef.current = err.message;
|
||||
}
|
||||
notifyError(err.message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
@@ -125,6 +130,7 @@ export function useInfiniteQuery<T>(
|
||||
// Function to be called by the UI to refetch the entire query from the beginning.
|
||||
const refetch = useCallback(() => {
|
||||
setIsRefetching(true);
|
||||
lastErrorMessageRef.current = null;
|
||||
setData([]);
|
||||
fetchPage(initialCursor);
|
||||
}, [fetchPage, initialCursor]);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/middleware/multer.middleware.test.ts
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
||||
import { describe, it, expect, vi, beforeEach, afterEach, Mock } from 'vitest';
|
||||
import multer from 'multer';
|
||||
import type { Request, Response, NextFunction } from 'express';
|
||||
import { createUploadMiddleware, handleMulterError } from './multer.middleware';
|
||||
|
||||
@@ -71,7 +71,7 @@ const imageFileFilter = (req: Request, file: Express.Multer.File, cb: multer.Fil
|
||||
} else {
|
||||
// Reject the file with a specific error that can be caught by a middleware.
|
||||
const validationIssue = { path: ['file', file.fieldname], message: 'Only image files are allowed!' };
|
||||
const err = new ValidationError([validationIssue]);
|
||||
const err = new ValidationError([validationIssue], 'Only image files are allowed!');
|
||||
cb(err as Error); // Cast to Error to satisfy multer's type, though ValidationError extends Error.
|
||||
}
|
||||
};
|
||||
|
||||
@@ -84,7 +84,11 @@ const emptySchema = z.object({});
|
||||
|
||||
const router = Router();
|
||||
|
||||
const upload = createUploadMiddleware({ storageType: 'flyer' });
|
||||
const brandLogoUpload = createUploadMiddleware({
|
||||
storageType: 'flyer', // Using flyer storage path is acceptable for brand logos.
|
||||
fileSize: 2 * 1024 * 1024, // 2MB limit for logos
|
||||
fileFilter: 'image',
|
||||
});
|
||||
|
||||
// --- Bull Board (Job Queue UI) Setup ---
|
||||
const serverAdapter = new ExpressAdapter();
|
||||
@@ -239,7 +243,7 @@ router.put(
|
||||
router.post(
|
||||
'/brands/:id/logo',
|
||||
validateRequest(numericIdParam('id')),
|
||||
upload.single('logoImage'),
|
||||
brandLogoUpload.single('logoImage'),
|
||||
requireFileUpload('logoImage'),
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const { params } = req as unknown as z.infer<ReturnType<typeof numericIdParam>>;
|
||||
|
||||
0
src/routes/personalization.db.ts
Normal file
0
src/routes/personalization.db.ts
Normal file
@@ -325,7 +325,7 @@ describe('AI API Client (Network Mocking with MSW)', () => {
|
||||
return HttpResponse.text('Gateway Timeout', { status: 504, statusText: 'Gateway Timeout' });
|
||||
}),
|
||||
);
|
||||
await expect(aiApiClient.getJobStatus(jobId)).rejects.toThrow('API Error: 504 Gateway Timeout');
|
||||
await expect(aiApiClient.getJobStatus(jobId)).rejects.toThrow('Gateway Timeout');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -24,10 +24,12 @@ describe('Admin API Routes Integration Tests', () => {
|
||||
email: `admin-integration-${Date.now()}@test.com`,
|
||||
role: 'admin',
|
||||
fullName: 'Admin Test User',
|
||||
request, // Pass supertest request to ensure user is created in the test DB
|
||||
}));
|
||||
({ user: regularUser, token: regularUserToken } = await createAndLoginUser({
|
||||
email: `regular-integration-${Date.now()}@test.com`,
|
||||
fullName: 'Regular User',
|
||||
request, // Pass supertest request
|
||||
}));
|
||||
|
||||
// Cleanup the created user after all tests in this file are done
|
||||
@@ -51,6 +53,10 @@ describe('Admin API Routes Integration Tests', () => {
|
||||
.get('/api/admin/stats')
|
||||
.set('Authorization', `Bearer ${adminToken}`);
|
||||
const stats = response.body;
|
||||
// DEBUG: Log response if it fails expectation
|
||||
if (response.status !== 200) {
|
||||
console.error('[DEBUG] GET /api/admin/stats failed:', response.status, response.body);
|
||||
}
|
||||
expect(stats).toBeDefined();
|
||||
expect(stats).toHaveProperty('flyerCount');
|
||||
expect(stats).toHaveProperty('userCount');
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('AI API Routes Integration Tests', () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
// Create and log in as a new user for authenticated tests.
|
||||
({ token: authToken } = await createAndLoginUser({ fullName: 'AI Tester' }));
|
||||
({ token: authToken } = await createAndLoginUser({ fullName: 'AI Tester', request }));
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -83,7 +83,11 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ items: [{ item: 'test' }] });
|
||||
const result = response.body;
|
||||
expect(response.status).toBe(404);
|
||||
// DEBUG: Log response if it fails expectation
|
||||
if (response.status !== 200 || !result.text) {
|
||||
console.log('[DEBUG] POST /api/ai/quick-insights response:', response.status, response.body);
|
||||
}
|
||||
expect(response.status).toBe(200);
|
||||
expect(result.text).toBe('This is a server-generated quick insight: buy the cheap stuff!');
|
||||
});
|
||||
|
||||
@@ -93,7 +97,11 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ items: [{ item: 'test' }] });
|
||||
const result = response.body;
|
||||
expect(response.status).toBe(404);
|
||||
// DEBUG: Log response if it fails expectation
|
||||
if (response.status !== 200 || !result.text) {
|
||||
console.log('[DEBUG] POST /api/ai/deep-dive response:', response.status, response.body);
|
||||
}
|
||||
expect(response.status).toBe(200);
|
||||
expect(result.text).toBe('This is a server-generated deep dive analysis. It is very detailed.');
|
||||
});
|
||||
|
||||
@@ -103,7 +111,11 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ query: 'test query' });
|
||||
const result = response.body;
|
||||
expect(response.status).toBe(404);
|
||||
// DEBUG: Log response if it fails expectation
|
||||
if (response.status !== 200 || !result.text) {
|
||||
console.log('[DEBUG] POST /api/ai/search-web response:', response.status, response.body);
|
||||
}
|
||||
expect(response.status).toBe(200);
|
||||
expect(result).toEqual({ text: 'The web says this is good.', sources: [] });
|
||||
});
|
||||
|
||||
@@ -141,6 +153,10 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ items: [], store: mockStore, userLocation: mockLocation });
|
||||
// The service for this endpoint is disabled and throws an error, which results in a 500.
|
||||
// DEBUG: Log response if it fails expectation
|
||||
if (response.status !== 500) {
|
||||
console.log('[DEBUG] POST /api/ai/plan-trip response:', response.status, response.body);
|
||||
}
|
||||
expect(response.status).toBe(500);
|
||||
const errorResult = response.body;
|
||||
expect(errorResult.message).toContain('planTripWithMaps');
|
||||
@@ -153,7 +169,7 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.post('/api/ai/generate-image')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ prompt: 'a test prompt' });
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.status).toBe(501);
|
||||
});
|
||||
|
||||
it('POST /api/ai/generate-speech should reject because it is not implemented', async () => {
|
||||
@@ -162,6 +178,6 @@ describe('AI API Routes Integration Tests', () => {
|
||||
.post('/api/ai/generate-speech')
|
||||
.set('Authorization', `Bearer ${authToken}`)
|
||||
.send({ text: 'a test prompt' });
|
||||
expect(response.status).toBe(404);
|
||||
expect(response.status).toBe(501);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('Authentication API Integration', () => {
|
||||
beforeAll(async () => {
|
||||
// Use a unique email for this test suite to prevent collisions with other tests.
|
||||
const email = `auth-integration-test-${Date.now()}@example.com`;
|
||||
({ user: testUser } = await createAndLoginUser({ email, fullName: 'Auth Test User' }));
|
||||
({ user: testUser } = await createAndLoginUser({ email, fullName: 'Auth Test User', request }));
|
||||
testUserEmail = testUser.user.email;
|
||||
});
|
||||
|
||||
@@ -43,6 +43,10 @@ describe('Authentication API Integration', () => {
|
||||
.send({ email: testUserEmail, password: TEST_PASSWORD, rememberMe: false });
|
||||
const data = response.body;
|
||||
|
||||
if (response.status !== 200) {
|
||||
console.error('[DEBUG] Login failed:', response.status, JSON.stringify(data, null, 2));
|
||||
}
|
||||
|
||||
// Assert that the API returns the expected structure
|
||||
expect(data).toBeDefined();
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
@@ -101,6 +101,11 @@ describe('Flyer Processing Background Job Integration Test', () => {
|
||||
}
|
||||
|
||||
// Assert 2: Check that the job completed successfully.
|
||||
if (jobStatus?.state === 'failed') {
|
||||
console.error('[DEBUG] Job failed with reason:', jobStatus.failedReason);
|
||||
console.error('[DEBUG] Job stack trace:', jobStatus.stacktrace);
|
||||
console.error('[DEBUG] Full Job Status:', JSON.stringify(jobStatus, null, 2));
|
||||
}
|
||||
expect(jobStatus?.state).toBe('completed');
|
||||
const flyerId = jobStatus?.returnValue?.flyerId;
|
||||
expect(flyerId).toBeTypeOf('number');
|
||||
@@ -132,6 +137,7 @@ describe('Flyer Processing Background Job Integration Test', () => {
|
||||
const { user: authUser, token } = await createAndLoginUser({
|
||||
email,
|
||||
fullName: 'Flyer Uploader',
|
||||
request,
|
||||
});
|
||||
createdUserIds.push(authUser.user.user_id); // Track for cleanup
|
||||
|
||||
|
||||
@@ -38,6 +38,29 @@ describe('Public API Routes Integration Tests', () => {
|
||||
fullName: 'Public Routes Test User',
|
||||
});
|
||||
testUser = createdUser;
|
||||
|
||||
// DEBUG: Verify user existence in DB
|
||||
console.log(`[DEBUG] createAndLoginUser returned user ID: ${testUser.user.user_id}`);
|
||||
const userCheck = await pool.query('SELECT user_id FROM public.users WHERE user_id = $1', [testUser.user.user_id]);
|
||||
console.log(`[DEBUG] DB check for user found ${userCheck.rowCount} rows.`);
|
||||
if (userCheck.rowCount === 0) {
|
||||
console.error(`[DEBUG] CRITICAL: User ${testUser.user.user_id} does not exist in public.users table! Attempting to wait...`);
|
||||
// Wait loop to ensure user persistence if there's a race condition
|
||||
for (let i = 0; i < 5; i++) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
const retryCheck = await pool.query('SELECT user_id FROM public.users WHERE user_id = $1', [testUser.user.user_id]);
|
||||
if (retryCheck.rowCount > 0) {
|
||||
console.log(`[DEBUG] User found after retry ${i + 1}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Final check before proceeding to avoid FK error
|
||||
const finalCheck = await pool.query('SELECT user_id FROM public.users WHERE user_id = $1', [testUser.user.user_id]);
|
||||
if (finalCheck.rowCount === 0) {
|
||||
throw new Error(`User ${testUser.user.user_id} failed to persist in DB. Cannot continue test.`);
|
||||
}
|
||||
|
||||
// Create a recipe
|
||||
const recipeRes = await pool.query(
|
||||
`INSERT INTO public.recipes (name, instructions, user_id, status) VALUES ('Public Test Recipe', 'Instructions here', $1, 'public') RETURNING *`,
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('User API Routes Integration Tests', () => {
|
||||
// The token will be used for all subsequent API calls in this test suite.
|
||||
beforeAll(async () => {
|
||||
const email = `user-test-${Date.now()}@example.com`;
|
||||
const { user, token } = await createAndLoginUser({ email, fullName: 'Test User' });
|
||||
const { user, token } = await createAndLoginUser({ email, fullName: 'Test User', request });
|
||||
testUser = user;
|
||||
authToken = token;
|
||||
});
|
||||
@@ -130,7 +130,7 @@ describe('User API Routes Integration Tests', () => {
|
||||
it('should allow a user to delete their own account and then fail to log in', async () => {
|
||||
// Arrange: Create a new, separate user just for this deletion test.
|
||||
const deletionEmail = `delete-me-${Date.now()}@example.com`;
|
||||
const { token: deletionToken } = await createAndLoginUser({ email: deletionEmail });
|
||||
const { token: deletionToken } = await createAndLoginUser({ email: deletionEmail, request });
|
||||
|
||||
// Act: Call the delete endpoint with the correct password and token.
|
||||
const response = await request
|
||||
@@ -155,7 +155,7 @@ describe('User API Routes Integration Tests', () => {
|
||||
it('should allow a user to reset their password and log in with the new one', async () => {
|
||||
// Arrange: Create a new user for the password reset flow.
|
||||
const resetEmail = `reset-me-${Date.now()}@example.com`;
|
||||
const { user: resetUser } = await createAndLoginUser({ email: resetEmail });
|
||||
const { user: resetUser } = await createAndLoginUser({ email: resetEmail, request });
|
||||
|
||||
// Act 1: Request a password reset. In our test environment, the token is returned in the response.
|
||||
const resetRequestRawResponse = await request
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('User Routes Integration Tests (/api/users)', () => {
|
||||
// Use the helper to create and log in a user in one step.
|
||||
const { user, token } = await createAndLoginUser({
|
||||
fullName: 'User Routes Test User',
|
||||
request,
|
||||
});
|
||||
testUser = user;
|
||||
authToken = token;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import * as apiClient from '../../services/apiClient';
|
||||
import { getPool } from '../../services/db/connection.db';
|
||||
import type { UserProfile } from '../../types';
|
||||
import supertest from 'supertest';
|
||||
|
||||
export const TEST_PASSWORD = 'a-much-stronger-password-for-testing-!@#$';
|
||||
|
||||
@@ -10,6 +11,8 @@ interface CreateUserOptions {
|
||||
password?: string;
|
||||
fullName?: string;
|
||||
role?: 'admin' | 'user';
|
||||
// Use ReturnType to match the actual return type of supertest(app) to avoid type mismatches (e.g. TestAgent vs SuperTest)
|
||||
request?: ReturnType<typeof supertest>;
|
||||
}
|
||||
|
||||
interface CreateUserResult {
|
||||
@@ -31,16 +34,53 @@ export const createAndLoginUser = async (
|
||||
const password = options.password || TEST_PASSWORD;
|
||||
const fullName = options.fullName || 'Test User';
|
||||
|
||||
await apiClient.registerUser(email, password, fullName);
|
||||
if (options.request) {
|
||||
// Use supertest for integration tests (hits the app instance directly)
|
||||
const registerRes = await options.request
|
||||
.post('/api/auth/register')
|
||||
.send({ email, password, full_name: fullName });
|
||||
|
||||
if (options.role === 'admin') {
|
||||
await getPool().query(
|
||||
`UPDATE public.profiles SET role = 'admin' FROM public.users WHERE public.profiles.user_id = public.users.user_id AND public.users.email = $1`,
|
||||
[email],
|
||||
);
|
||||
if (registerRes.status !== 201 && registerRes.status !== 200) {
|
||||
throw new Error(
|
||||
`Failed to register user via supertest: ${registerRes.status} ${JSON.stringify(registerRes.body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (options.role === 'admin') {
|
||||
await getPool().query(
|
||||
`UPDATE public.profiles SET role = 'admin' FROM public.users WHERE public.profiles.user_id = public.users.user_id AND public.users.email = $1`,
|
||||
[email],
|
||||
);
|
||||
}
|
||||
|
||||
const loginRes = await options.request
|
||||
.post('/api/auth/login')
|
||||
.send({ email, password, rememberMe: false });
|
||||
|
||||
if (loginRes.status !== 200) {
|
||||
throw new Error(
|
||||
`Failed to login user via supertest: ${loginRes.status} ${JSON.stringify(loginRes.body)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { userprofile, token } = loginRes.body;
|
||||
return { user: userprofile, token };
|
||||
} else {
|
||||
// Use apiClient for E2E tests (hits the external URL via fetch)
|
||||
await apiClient.registerUser(email, password, fullName);
|
||||
|
||||
if (options.role === 'admin') {
|
||||
await getPool().query(
|
||||
`UPDATE public.profiles SET role = 'admin' FROM public.users WHERE public.profiles.user_id = public.users.user_id AND public.users.email = $1`,
|
||||
[email],
|
||||
);
|
||||
}
|
||||
|
||||
const loginResponse = await apiClient.loginUser(email, password, false);
|
||||
if (!loginResponse.ok) {
|
||||
throw new Error(`Failed to login user via apiClient: ${loginResponse.status}`);
|
||||
}
|
||||
const { userprofile, token } = await loginResponse.json();
|
||||
return { user: userprofile, token };
|
||||
}
|
||||
|
||||
const loginResponse = await apiClient.loginUser(email, password, false);
|
||||
const { userprofile, token } = await loginResponse.json();
|
||||
return { user: userprofile, token };
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user