Compare commits

..

3 Commits

Author SHA1 Message Date
Gitea Actions
c4742959e4 ci: Bump version to 0.2.3 [skip ci] 2025-12-27 09:17:15 +05:00
97c54c0c5c Merge branch 'main' of https://gitea.projectium.com/torbo/flyer-crawler.projectium.com
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m48s
2025-12-26 20:16:33 -08:00
7cc50907d1 more testing fixes 2025-12-26 20:16:19 -08:00
7 changed files with 33 additions and 17 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "flyer-crawler",
"version": "0.2.2",
"version": "0.2.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "flyer-crawler",
"version": "0.2.2",
"version": "0.2.3",
"dependencies": {
"@bull-board/api": "^6.14.2",
"@bull-board/express": "^6.14.2",

View File

@@ -1,7 +1,7 @@
{
"name": "flyer-crawler",
"private": true,
"version": "0.2.2",
"version": "0.2.3",
"type": "module",
"scripts": {
"dev": "concurrently \"npm:start:dev\" \"vite\"",

View File

@@ -61,9 +61,8 @@ describe('FlyerUploader', () => {
beforeEach(() => {
console.log(`\n--- [TEST LOG] ---: Starting test: "${expect.getState().currentTestName}"`);
// Use the 'modern' implementation of fake timers to handle promise microtasks correctly.
vi.useFakeTimers({ toFake: ['setTimeout'], shouldAdvanceTime: true });
console.log('--- [TEST LOG] ---: MODERN fake timers enabled.');
// Use fake timers to control polling intervals.
vi.useFakeTimers();
vi.resetAllMocks(); // Resets mock implementations AND call history.
console.log('--- [TEST LOG] ---: Mocks reset.');
mockedChecksumModule.generateFileChecksum.mockResolvedValue('mock-checksum');
@@ -358,7 +357,7 @@ describe('FlyerUploader', () => {
try {
console.log('--- [TEST LOG] ---: 4. AWAITING duplicate flyer message...');
expect(
await screen.findByText('This flyer has already been processed. You can view it here:'),
await screen.findByText(/This flyer has already been processed/i),
).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 5. SUCCESS: Duplicate message found.');
} catch (error) {
@@ -471,7 +470,7 @@ describe('FlyerUploader', () => {
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. Awaiting error message.');
expect(await screen.findByText(/Polling Network Error/i)).toBeInTheDocument();
expect(await screen.findByText(/Polling failed: Polling Network Error/i)).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 4. Assertions passed.');
});
@@ -513,7 +512,9 @@ describe('FlyerUploader', () => {
fireEvent.change(input, { target: { files: [file] } });
console.log('--- [TEST LOG] ---: 3. Awaiting error message.');
expect(await screen.findByText(/Failed to parse JSON response from server/i)).toBeInTheDocument();
expect(
await screen.findByText(/Polling failed: Failed to parse JSON response from server/i),
).toBeInTheDocument();
console.log('--- [TEST LOG] ---: 4. Assertions passed.');
});

View File

@@ -87,7 +87,9 @@ export const FlyerUploader: React.FC<FlyerUploaderProps> = ({ onProcessingComple
)}
{processingState === 'completed' && (
<p className="text-green-600 dark:text-green-400 mt-2 font-bold">Processing complete! Redirecting...</p>
<p className="text-green-600 dark:text-green-400 mt-2 font-bold">
Processing complete! Redirecting to flyer {flyerId}...
</p>
)}
{errorMessage && (
@@ -95,7 +97,8 @@ export const FlyerUploader: React.FC<FlyerUploaderProps> = ({ onProcessingComple
<p>{errorMessage}</p>
{duplicateFlyerId && (
<p>
<Link to={`/flyers/${duplicateFlyerId}`} className="text-blue-500 underline">
This flyer has already been processed. You can view it here:{' '}
<Link to={`/flyers/${duplicateFlyerId}`} className="text-blue-500 underline" data-discover="true">
Flyer #{duplicateFlyerId}
</Link>
</p>

View File

@@ -5,6 +5,11 @@ import { useFlyerUploader } from './useFlyerUploader';
import * as aiApiClient from '../services/aiApiClient';
import * as checksumUtil from '../utils/checksum';
// Import the actual error class because the module is mocked
const { JobFailedError } = await vi.importActual<typeof import('../services/aiApiClient')>(
'../services/aiApiClient',
);
// Mock dependencies
vi.mock('../services/aiApiClient');
vi.mock('../utils/checksum');
@@ -111,11 +116,9 @@ describe('useFlyerUploader Hook with React Query', () => {
mockedAiApiClient.uploadAndProcessFlyer.mockResolvedValue({ jobId: mockJobId });
// Mock getJobStatus to throw a JobFailedError
const jobFailedError = new aiApiClient.JobFailedError(
'AI validation failed.',
'AI_VALIDATION_FAILED',
mockedAiApiClient.getJobStatus.mockRejectedValue(
new JobFailedError('AI validation failed.', 'AI_VALIDATION_FAILED'),
);
mockedAiApiClient.getJobStatus.mockRejectedValue(jobFailedError);
const { result } = renderHook(() => useFlyerUploader(), { wrapper: createWrapper() });
const mockFile = new File([''], 'flyer.pdf');

View File

@@ -86,7 +86,13 @@ export const useFlyerUploader = () => {
if (uploadMutation.isPending) return 'uploading';
if (jobStatus && (jobStatus.state === 'active' || jobStatus.state === 'waiting'))
return 'polling';
if (jobStatus?.state === 'completed') return 'completed';
if (jobStatus?.state === 'completed') {
// If the job is complete but didn't return a flyerId, it's an error state.
if (!jobStatus.returnValue?.flyerId) {
return 'error';
}
return 'completed';
}
if (uploadMutation.isError || jobStatus?.state === 'failed' || pollError) return 'error';
return 'idle';
})();
@@ -100,6 +106,9 @@ export const useFlyerUploader = () => {
if (jobStatus?.state === 'failed') {
return `Processing failed: ${jobStatus.progress?.message || jobStatus.failedReason}`;
}
if (jobStatus?.state === 'completed' && !jobStatus.returnValue?.flyerId) {
return 'Job completed but did not return a flyer ID.';
}
return null;
};

View File

@@ -380,7 +380,7 @@ describe('FlyerProcessingService', () => {
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.',
'Unsupported file type: .txt. Supported types are PDF, JPG, PNG, WEBP, HEIC, HEIF, GIF, TIFF, SVG, BMP.',
});
expect(mockCleanupQueue.add).not.toHaveBeenCalled();
});