Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 210 additions & 1 deletion src/services/drive/file/utils/exifHelpers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
jest.mock('../..', () => ({
__esModule: true,
default: {
file: {
getExtensionFromUri: jest.fn(),
},
},
}));

import { isTemporaryFileName, parseExifDate } from './exifHelpers';
import drive from '../..';
import {
clearGeneratedNamesCache,
generateFileName,
isTemporaryFileName,
isVideoExtension,
parseExifDate,
} from './exifHelpers';

const mockGetExtensionFromUri = drive.file.getExtensionFromUri as jest.MockedFunction<
typeof drive.file.getExtensionFromUri
>;

describe('parseExifDate', () => {
test('should parse valid EXIF date format', () => {
Expand Down Expand Up @@ -36,6 +48,47 @@ describe('parseExifDate', () => {
});
});

describe('isVideoExtension', () => {
test('should return true for video extensions', () => {
expect(isVideoExtension('mp4')).toBe(true);
expect(isVideoExtension('mov')).toBe(true);
expect(isVideoExtension('avi')).toBe(true);
});

test('should return false for image extensions', () => {
expect(isVideoExtension('jpg')).toBe(false);
expect(isVideoExtension('png')).toBe(false);
expect(isVideoExtension('heic')).toBe(false);
expect(isVideoExtension('gif')).toBe(false);
});

test('should handle uppercase extensions', () => {
expect(isVideoExtension('MP4')).toBe(true);
expect(isVideoExtension('MOV')).toBe(true);
expect(isVideoExtension('JPG')).toBe(false);
});

test('should return false for other file types', () => {
expect(isVideoExtension('pdf')).toBe(false);
expect(isVideoExtension('txt')).toBe(false);
expect(isVideoExtension('doc')).toBe(false);
});

test('should return true for additional video formats', () => {
expect(isVideoExtension('mkv')).toBe(true);
expect(isVideoExtension('webm')).toBe(true);
expect(isVideoExtension('m4v')).toBe(true);
expect(isVideoExtension('3gp')).toBe(true);
expect(isVideoExtension('flv')).toBe(true);
});

test('should return false for unsupported extensions', () => {
expect(isVideoExtension('xyz')).toBe(false);
expect(isVideoExtension('unknown')).toBe(false);
expect(isVideoExtension('abc')).toBe(false);
});
});

describe('isTemporaryFileName', () => {
test('should return true for numeric filenames', () => {
expect(isTemporaryFileName('12345.jpg')).toBe(true);
Expand Down Expand Up @@ -73,3 +126,159 @@ describe('isTemporaryFileName', () => {
expect(isTemporaryFileName('12345')).toBe(false);
});
});

describe('generateFileName', () => {
beforeEach(() => {
jest.clearAllMocks();
clearGeneratedNamesCache();
mockGetExtensionFromUri.mockReturnValue('jpg');
});

test('should generate filename without milliseconds for first file', () => {
const uri = 'file:///path/to/image.jpg';
const creationTime = '2024-12-25T14:30:45.123Z';

const result = generateFileName(uri, creationTime);

expect(result).toBe('IMG_20241225_143045.jpg');
expect(mockGetExtensionFromUri).toHaveBeenCalledWith(uri);
});

test('should use modificationTime over creationTime', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a comment. It is better to use non technical/constants names so if the variable name changes, you don't need to update the test. E.g. test('When the file is generated, then the correct date and time is used').

const uri = 'file:///path/to/image.jpg';
const creationTime = '2024-12-25T14:30:45.000Z';
const modificationTime = '2024-12-25T15:30:45.000Z';

const result = generateFileName(uri, creationTime, modificationTime);

expect(result).toContain('153045');
});

test('should handle duplicate timestamps by adding milliseconds then counter', () => {
const uri1 = 'file:///path/to/image1.jpg';
const uri2 = 'file:///path/to/image2.jpg';
const uri3 = 'file:///path/to/image3.jpg';
const sameTimestamp = '2024-12-25T14:30:45.123Z';

const result1 = generateFileName(uri1, sameTimestamp);
const result2 = generateFileName(uri2, sameTimestamp);
const result3 = generateFileName(uri3, sameTimestamp);

expect(result1).toBe('IMG_20241225_143045.jpg');
expect(result2).toBe('IMG_20241225_143045-123.jpg');
expect(result3).toBe('IMG_20241225_143045-123_2.jpg');
});

test('should fallback to Date.now() when no time provided', () => {
const uri = 'file:///path/to/image.jpg';

const result = generateFileName(uri);

expect(result).toMatch(/^IMG_\d{8}_\d{6}\.jpg$/);
});

test('should handle invalid dates and fallback to current time', () => {
const uri = 'file:///path/to/image.jpg';
const invalidTime = 'invalid-date';

const result = generateFileName(uri, invalidTime);

expect(result).toMatch(/^IMG_\d{8}_\d{6}\.jpg$/);
});

test('should handle different file extensions', () => {
mockGetExtensionFromUri.mockReturnValue('png');
const uri = 'file:///path/to/image.png';
const creationTime = '2024-12-25T14:30:45.123Z';

const result = generateFileName(uri, creationTime);

expect(result).toMatch(/\.png$/);
});

test('should use VID prefix for video files', () => {
mockGetExtensionFromUri.mockReturnValue('mp4');
const uri = 'file:///path/to/video.mp4';
const creationTime = '2024-12-25T14:30:45.123Z';

const result = generateFileName(uri, creationTime);

expect(result).toBe('VID_20241225_143045.mp4');
});

test('should use VID prefix for MOV files', () => {
mockGetExtensionFromUri.mockReturnValue('mov');
const uri = 'file:///path/to/video.mov';
const creationTime = '2024-12-25T14:30:45.123Z';

const result = generateFileName(uri, creationTime);

expect(result).toBe('VID_20241225_143045.mov');
});

test('should use IMG prefix for image files', () => {
mockGetExtensionFromUri.mockReturnValue('heic');
const uri = 'file:///path/to/photo.heic';
const creationTime = '2024-12-25T14:30:45.123Z';

const result = generateFileName(uri, creationTime);

expect(result).toBe('IMG_20241225_143045.heic');
});

test('should use fallback when formatting fails', () => {
const uri = 'file:///path/to/image.jpg';

const originalToISOString = Date.prototype.toISOString;
let callCount = 0;
Date.prototype.toISOString = jest.fn(() => {
callCount++;
if (callCount === 1) {
throw new Error('Formatting error');
}
return originalToISOString.call(new Date());
});

const result = generateFileName(uri);

expect(result).toMatch(/^IMG_\d{8}_\d{6}\.jpg$/);

Date.prototype.toISOString = originalToISOString;
});

test('should handle bulk upload scenario with 20 files', () => {
const sameTimestamp = '2024-12-25T14:30:45.123Z';
const results: string[] = [];

for (let i = 0; i < 20; i++) {
const uri = `file:///path/to/image${i}.jpg`;
results.push(generateFileName(uri, sameTimestamp));
}

const uniqueNames = new Set(results);
expect(uniqueNames.size).toBe(20);

expect(results[0]).toBe('IMG_20241225_143045.jpg');
expect(results[1]).toBe('IMG_20241225_143045-123.jpg');
expect(results[2]).toBe('IMG_20241225_143045-123_2.jpg');
expect(results[19]).toBe('IMG_20241225_143045-123_19.jpg');
});

test('should handle duplicate video files with VID prefix', () => {
mockGetExtensionFromUri.mockReturnValue('mp4');
clearGeneratedNamesCache();

const uri1 = 'file:///path/to/video1.mp4';
const uri2 = 'file:///path/to/video2.mp4';
const uri3 = 'file:///path/to/video3.mp4';
const sameTimestamp = '2024-12-25T14:30:45.456Z';

const result1 = generateFileName(uri1, sameTimestamp);
const result2 = generateFileName(uri2, sameTimestamp);
const result3 = generateFileName(uri3, sameTimestamp);

expect(result1).toBe('VID_20241225_143045.mp4');
expect(result2).toBe('VID_20241225_143045-456.mp4');
expect(result3).toBe('VID_20241225_143045-456_2.mp4');
});
});
Loading
Loading