some pm2 fiex + unit test work for new flyeruploader
This commit is contained in:
@@ -138,6 +138,10 @@ jobs:
|
|||||||
cd /var/www/flyer-crawler.projectium.com
|
cd /var/www/flyer-crawler.projectium.com
|
||||||
npm install --omit=dev
|
npm install --omit=dev
|
||||||
|
|
||||||
|
# --- Cleanup Errored Processes ---
|
||||||
|
echo "Cleaning up errored or stopped PM2 processes..."
|
||||||
|
node -e "const exec = require('child_process').execSync; try { const list = JSON.parse(exec('pm2 jlist').toString()); list.forEach(p => { if (p.pm2_env.status === 'errored' || p.pm2_env.status === 'stopped') { console.log('Deleting ' + p.pm2_env.status + ' process: ' + p.name + ' (' + p.pm2_env.pm_id + ')'); try { exec('pm2 delete ' + p.pm2_env.pm_id); } catch(e) { console.error('Failed to delete ' + p.pm2_env.pm_id); } } }); } catch (e) { console.error('Error cleaning up processes:', e); }"
|
||||||
|
|
||||||
# --- Version Check Logic ---
|
# --- Version Check Logic ---
|
||||||
# Get the version from the newly deployed package.json
|
# Get the version from the newly deployed package.json
|
||||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
|||||||
@@ -397,6 +397,11 @@ jobs:
|
|||||||
echo "Installing production dependencies and restarting test server..."
|
echo "Installing production dependencies and restarting test server..."
|
||||||
cd /var/www/flyer-crawler-test.projectium.com
|
cd /var/www/flyer-crawler-test.projectium.com
|
||||||
npm install --omit=dev
|
npm install --omit=dev
|
||||||
|
|
||||||
|
# --- Cleanup Errored Processes ---
|
||||||
|
echo "Cleaning up errored or stopped PM2 processes..."
|
||||||
|
node -e "const exec = require('child_process').execSync; try { const list = JSON.parse(exec('pm2 jlist').toString()); list.forEach(p => { if (p.pm2_env.status === 'errored' || p.pm2_env.status === 'stopped') { console.log('Deleting ' + p.pm2_env.status + ' process: ' + p.name + ' (' + p.pm2_env.pm_id + ')'); try { exec('pm2 delete ' + p.pm2_env.pm_id); } catch(e) { console.error('Failed to delete ' + p.pm2_env.pm_id); } } }); } catch (e) { console.error('Error cleaning up processes:', e); }"
|
||||||
|
|
||||||
# Use `startOrReload` with the ecosystem file. This is the standard, idempotent way to deploy.
|
# Use `startOrReload` with the ecosystem file. This is the standard, idempotent way to deploy.
|
||||||
# It will START the process if it's not running, or RELOAD it if it is.
|
# It will START the process if it's not running, or RELOAD it if it is.
|
||||||
# We also add `&& pm2 save` to persist the process list across server reboots.
|
# We also add `&& pm2 save` to persist the process list across server reboots.
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ jobs:
|
|||||||
cd /var/www/flyer-crawler.projectium.com
|
cd /var/www/flyer-crawler.projectium.com
|
||||||
npm install --omit=dev
|
npm install --omit=dev
|
||||||
|
|
||||||
|
# --- Cleanup Errored Processes ---
|
||||||
|
echo "Cleaning up errored or stopped PM2 processes..."
|
||||||
|
node -e "const exec = require('child_process').execSync; try { const list = JSON.parse(exec('pm2 jlist').toString()); list.forEach(p => { if (p.pm2_env.status === 'errored' || p.pm2_env.status === 'stopped') { console.log('Deleting ' + p.pm2_env.status + ' process: ' + p.name + ' (' + p.pm2_env.pm_id + ')'); try { exec('pm2 delete ' + p.pm2_env.pm_id); } catch(e) { console.error('Failed to delete ' + p.pm2_env.pm_id); } } }); } catch (e) { console.error('Error cleaning up processes:', e); }"
|
||||||
|
|
||||||
# --- Version Check Logic ---
|
# --- Version Check Logic ---
|
||||||
# Get the version from the newly deployed package.json
|
# Get the version from the newly deployed package.json
|
||||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ module.exports = {
|
|||||||
name: 'flyer-crawler-api',
|
name: 'flyer-crawler-api',
|
||||||
script: './node_modules/.bin/tsx',
|
script: './node_modules/.bin/tsx',
|
||||||
args: 'server.ts', // tsx will execute this file
|
args: 'server.ts', // tsx will execute this file
|
||||||
|
max_memory_restart: '500M', // Restart if memory usage exceeds 500MB
|
||||||
// Production Environment Settings
|
// Production Environment Settings
|
||||||
env_production: {
|
env_production: {
|
||||||
NODE_ENV: 'production', // Set the Node.js environment to production
|
NODE_ENV: 'production', // Set the Node.js environment to production
|
||||||
@@ -89,6 +90,7 @@ module.exports = {
|
|||||||
name: 'flyer-crawler-worker',
|
name: 'flyer-crawler-worker',
|
||||||
script: './node_modules/.bin/tsx',
|
script: './node_modules/.bin/tsx',
|
||||||
args: 'src/services/worker.ts', // tsx will execute this file
|
args: 'src/services/worker.ts', // tsx will execute this file
|
||||||
|
max_memory_restart: '1G', // Restart if memory usage exceeds 1GB
|
||||||
// Production Environment Settings
|
// Production Environment Settings
|
||||||
env_production: {
|
env_production: {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
@@ -165,6 +167,7 @@ module.exports = {
|
|||||||
name: 'flyer-crawler-analytics-worker',
|
name: 'flyer-crawler-analytics-worker',
|
||||||
script: './node_modules/.bin/tsx',
|
script: './node_modules/.bin/tsx',
|
||||||
args: 'src/services/worker.ts', // tsx will execute this file
|
args: 'src/services/worker.ts', // tsx will execute this file
|
||||||
|
max_memory_restart: '1G', // Restart if memory usage exceeds 1GB
|
||||||
// Production Environment Settings
|
// Production Environment Settings
|
||||||
env_production: {
|
env_production: {
|
||||||
NODE_ENV: 'production',
|
NODE_ENV: 'production',
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { FlyerUploader } from './FlyerUploader';
|
|||||||
import * as aiApiClientModule from '../../services/aiApiClient';
|
import * as aiApiClientModule from '../../services/aiApiClient';
|
||||||
import * as checksumModule from '../../utils/checksum';
|
import * as checksumModule from '../../utils/checksum';
|
||||||
import { useNavigate, MemoryRouter } from 'react-router-dom';
|
import { useNavigate, MemoryRouter } from 'react-router-dom';
|
||||||
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||||
|
|
||||||
// Mock dependencies
|
// Mock dependencies
|
||||||
vi.mock('../../services/aiApiClient');
|
vi.mock('../../services/aiApiClient');
|
||||||
@@ -39,10 +40,19 @@ const mockedChecksumModule = checksumModule as unknown as {
|
|||||||
|
|
||||||
const renderComponent = (onProcessingComplete = vi.fn()) => {
|
const renderComponent = (onProcessingComplete = vi.fn()) => {
|
||||||
console.log('--- [TEST LOG] ---: Rendering component inside MemoryRouter.');
|
console.log('--- [TEST LOG] ---: Rendering component inside MemoryRouter.');
|
||||||
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
return render(
|
return render(
|
||||||
<MemoryRouter>
|
<QueryClientProvider client={queryClient}>
|
||||||
<FlyerUploader onProcessingComplete={onProcessingComplete} />
|
<MemoryRouter>
|
||||||
</MemoryRouter>,
|
<FlyerUploader onProcessingComplete={onProcessingComplete} />
|
||||||
|
</MemoryRouter>
|
||||||
|
</QueryClientProvider>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -224,7 +234,11 @@ describe('FlyerUploader', () => {
|
|||||||
mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue({ jobId: 'job-fail' });
|
mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue({ jobId: 'job-fail' });
|
||||||
mockedAiApiClient.getJobStatus.mockResolvedValue({
|
mockedAiApiClient.getJobStatus.mockResolvedValue({
|
||||||
state: 'failed',
|
state: 'failed',
|
||||||
failedReason: 'AI model exploded',
|
progress: {
|
||||||
|
errorCode: 'UNKNOWN_ERROR',
|
||||||
|
message: 'AI model exploded',
|
||||||
|
},
|
||||||
|
failedReason: 'This is the raw error message.', // The UI should prefer the progress message.
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('--- [TEST LOG] ---: 2. Rendering and uploading.');
|
console.log('--- [TEST LOG] ---: 2. Rendering and uploading.');
|
||||||
@@ -259,7 +273,11 @@ describe('FlyerUploader', () => {
|
|||||||
// We need at least one 'active' response to establish a timeout loop so we have something to clear
|
// We need at least one 'active' response to establish a timeout loop so we have something to clear
|
||||||
mockedAiApiClient.getJobStatus
|
mockedAiApiClient.getJobStatus
|
||||||
.mockResolvedValueOnce({ state: 'active', progress: { message: 'Working...' } })
|
.mockResolvedValueOnce({ state: 'active', progress: { message: 'Working...' } })
|
||||||
.mockResolvedValueOnce({ state: 'failed', failedReason: 'Fatal Error' });
|
.mockResolvedValueOnce({
|
||||||
|
state: 'failed',
|
||||||
|
progress: { errorCode: 'UNKNOWN_ERROR', message: 'Fatal Error' },
|
||||||
|
failedReason: 'Fatal Error',
|
||||||
|
});
|
||||||
|
|
||||||
renderComponent();
|
renderComponent();
|
||||||
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
|
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ const createWrapper = () => {
|
|||||||
|
|
||||||
describe('useFlyerUploader Hook with React Query', () => {
|
describe('useFlyerUploader Hook with React Query', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.resetAllMocks();
|
||||||
mockedChecksumUtil.generateFileChecksum.mockResolvedValue('mock-checksum');
|
mockedChecksumUtil.generateFileChecksum.mockResolvedValue('mock-checksum');
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,7 +78,7 @@ describe('useFlyerUploader Hook with React Query', () => {
|
|||||||
await waitFor(() => expect(result.current.statusMessage).toBe('Processing...'));
|
await waitFor(() => expect(result.current.statusMessage).toBe('Processing...'));
|
||||||
|
|
||||||
// Assert completed state
|
// Assert completed state
|
||||||
await waitFor(() => expect(result.current.processingState).toBe('completed'));
|
await waitFor(() => expect(result.current.processingState).toBe('completed'), { timeout: 5000 });
|
||||||
expect(result.current.flyerId).toBe(777);
|
expect(result.current.flyerId).toBe(777);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -248,7 +248,10 @@ describe('FlyerProcessingService', () => {
|
|||||||
|
|
||||||
await expect(service.processJob(job)).rejects.toThrow('AI model exploded');
|
await expect(service.processJob(job)).rejects.toThrow('AI model exploded');
|
||||||
|
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({ message: 'Error: AI model exploded' });
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
|
errorCode: 'UNKNOWN_ERROR',
|
||||||
|
message: 'AI model exploded',
|
||||||
|
});
|
||||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -260,7 +263,11 @@ describe('FlyerProcessingService', () => {
|
|||||||
|
|
||||||
await expect(service.processJob(job)).rejects.toThrow(conversionError);
|
await expect(service.processJob(job)).rejects.toThrow(conversionError);
|
||||||
|
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({ message: 'Error: Conversion failed' });
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
|
errorCode: 'PDF_CONVERSION_FAILED',
|
||||||
|
message:
|
||||||
|
'The uploaded PDF could not be processed. It might be blank, corrupt, or password-protected.',
|
||||||
|
});
|
||||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -280,7 +287,11 @@ describe('FlyerProcessingService', () => {
|
|||||||
{ err: validationError, validationErrors: {}, rawData: {} },
|
{ err: validationError, validationErrors: {}, rawData: {} },
|
||||||
'AI Data Validation failed.',
|
'AI Data Validation failed.',
|
||||||
);
|
);
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({ message: 'Error: Validation failed' });
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
|
errorCode: 'AI_VALIDATION_FAILED',
|
||||||
|
message:
|
||||||
|
"The AI couldn't read the flyer's format. Please try a clearer image or a different flyer.",
|
||||||
|
});
|
||||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,7 +364,8 @@ describe('FlyerProcessingService', () => {
|
|||||||
await expect(service.processJob(job)).rejects.toThrow('Database transaction failed');
|
await expect(service.processJob(job)).rejects.toThrow('Database transaction failed');
|
||||||
|
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
message: 'Error: Database transaction failed',
|
errorCode: 'UNKNOWN_ERROR',
|
||||||
|
message: 'Database transaction failed',
|
||||||
});
|
});
|
||||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
@@ -366,6 +378,7 @@ describe('FlyerProcessingService', () => {
|
|||||||
|
|
||||||
await expect(service.processJob(job)).rejects.toThrow(UnsupportedFileTypeError);
|
await expect(service.processJob(job)).rejects.toThrow(UnsupportedFileTypeError);
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
|
errorCode: 'UNSUPPORTED_FILE_TYPE',
|
||||||
message:
|
message:
|
||||||
'Error: Unsupported file type: .txt. Supported types are PDF, JPG, PNG, WEBP, HEIC, HEIF, GIF, TIFF, SVG, BMP.',
|
'Error: Unsupported file type: .txt. Supported types are PDF, JPG, PNG, WEBP, HEIC, HEIF, GIF, TIFF, SVG, BMP.',
|
||||||
});
|
});
|
||||||
@@ -390,7 +403,8 @@ describe('FlyerProcessingService', () => {
|
|||||||
await expect(service.processJob(job)).rejects.toThrow('Icon generation failed.');
|
await expect(service.processJob(job)).rejects.toThrow('Icon generation failed.');
|
||||||
|
|
||||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||||
message: 'Error: Icon generation failed.',
|
errorCode: 'UNKNOWN_ERROR',
|
||||||
|
message: 'Icon generation failed.',
|
||||||
});
|
});
|
||||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user