Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b4199f7c48 | ||
| dda36f7bc5 | |||
| 27810bbb36 |
@@ -138,6 +138,10 @@ jobs:
|
||||
cd /var/www/flyer-crawler.projectium.com
|
||||
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 ---
|
||||
# Get the version from the newly deployed package.json
|
||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
@@ -397,6 +397,11 @@ jobs:
|
||||
echo "Installing production dependencies and restarting test server..."
|
||||
cd /var/www/flyer-crawler-test.projectium.com
|
||||
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.
|
||||
# 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.
|
||||
|
||||
@@ -137,6 +137,10 @@ jobs:
|
||||
cd /var/www/flyer-crawler.projectium.com
|
||||
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 ---
|
||||
# Get the version from the newly deployed package.json
|
||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||
|
||||
@@ -13,6 +13,7 @@ module.exports = {
|
||||
name: 'flyer-crawler-api',
|
||||
script: './node_modules/.bin/tsx',
|
||||
args: 'server.ts', // tsx will execute this file
|
||||
max_memory_restart: '500M', // Restart if memory usage exceeds 500MB
|
||||
// Production Environment Settings
|
||||
env_production: {
|
||||
NODE_ENV: 'production', // Set the Node.js environment to production
|
||||
@@ -89,6 +90,7 @@ module.exports = {
|
||||
name: 'flyer-crawler-worker',
|
||||
script: './node_modules/.bin/tsx',
|
||||
args: 'src/services/worker.ts', // tsx will execute this file
|
||||
max_memory_restart: '1G', // Restart if memory usage exceeds 1GB
|
||||
// Production Environment Settings
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
@@ -165,6 +167,7 @@ module.exports = {
|
||||
name: 'flyer-crawler-analytics-worker',
|
||||
script: './node_modules/.bin/tsx',
|
||||
args: 'src/services/worker.ts', // tsx will execute this file
|
||||
max_memory_restart: '1G', // Restart if memory usage exceeds 1GB
|
||||
// Production Environment Settings
|
||||
env_production: {
|
||||
NODE_ENV: 'production',
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "flyer-crawler",
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"dependencies": {
|
||||
"@bull-board/api": "^6.14.2",
|
||||
"@bull-board/express": "^6.14.2",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "flyer-crawler",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"version": "0.2.2",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:start:dev\" \"vite\"",
|
||||
|
||||
@@ -6,6 +6,7 @@ import { FlyerUploader } from './FlyerUploader';
|
||||
import * as aiApiClientModule from '../../services/aiApiClient';
|
||||
import * as checksumModule from '../../utils/checksum';
|
||||
import { useNavigate, MemoryRouter } from 'react-router-dom';
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock('../../services/aiApiClient');
|
||||
@@ -39,10 +40,19 @@ const mockedChecksumModule = checksumModule as unknown as {
|
||||
|
||||
const renderComponent = (onProcessingComplete = vi.fn()) => {
|
||||
console.log('--- [TEST LOG] ---: Rendering component inside MemoryRouter.');
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
return render(
|
||||
<MemoryRouter>
|
||||
<FlyerUploader onProcessingComplete={onProcessingComplete} />
|
||||
</MemoryRouter>,
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter>
|
||||
<FlyerUploader onProcessingComplete={onProcessingComplete} />
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -224,7 +234,11 @@ describe('FlyerUploader', () => {
|
||||
mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue({ jobId: 'job-fail' });
|
||||
mockedAiApiClient.getJobStatus.mockResolvedValue({
|
||||
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.');
|
||||
@@ -259,7 +273,11 @@ describe('FlyerUploader', () => {
|
||||
// We need at least one 'active' response to establish a timeout loop so we have something to clear
|
||||
mockedAiApiClient.getJobStatus
|
||||
.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();
|
||||
const file = new File(['content'], 'flyer.pdf', { type: 'application/pdf' });
|
||||
|
||||
@@ -36,7 +36,7 @@ const createWrapper = () => {
|
||||
|
||||
describe('useFlyerUploader Hook with React Query', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
vi.resetAllMocks();
|
||||
mockedChecksumUtil.generateFileChecksum.mockResolvedValue('mock-checksum');
|
||||
});
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('useFlyerUploader Hook with React Query', () => {
|
||||
await waitFor(() => expect(result.current.statusMessage).toBe('Processing...'));
|
||||
|
||||
// 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);
|
||||
});
|
||||
|
||||
|
||||
@@ -248,7 +248,10 @@ describe('FlyerProcessingService', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -260,7 +263,11 @@ describe('FlyerProcessingService', () => {
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -280,7 +287,11 @@ describe('FlyerProcessingService', () => {
|
||||
{ err: validationError, validationErrors: {}, rawData: {} },
|
||||
'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();
|
||||
});
|
||||
|
||||
@@ -353,7 +364,8 @@ describe('FlyerProcessingService', () => {
|
||||
await expect(service.processJob(job)).rejects.toThrow('Database transaction failed');
|
||||
|
||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||
message: 'Error: Database transaction failed',
|
||||
errorCode: 'UNKNOWN_ERROR',
|
||||
message: 'Database transaction failed',
|
||||
});
|
||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||
});
|
||||
@@ -366,6 +378,7 @@ describe('FlyerProcessingService', () => {
|
||||
|
||||
await expect(service.processJob(job)).rejects.toThrow(UnsupportedFileTypeError);
|
||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||
errorCode: 'UNSUPPORTED_FILE_TYPE',
|
||||
message:
|
||||
'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.');
|
||||
|
||||
expect(job.updateProgress).toHaveBeenCalledWith({
|
||||
message: 'Error: Icon generation failed.',
|
||||
errorCode: 'UNKNOWN_ERROR',
|
||||
message: 'Icon generation failed.',
|
||||
});
|
||||
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user