test(services): add tests for service layer functions

This commit is contained in:
xtrullor73
2024-07-04 11:33:04 -07:00
parent e7b6957045
commit ef91981fb0
9 changed files with 573 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
import path from 'path';
describe('validateAudioFiles', function() {
let validateAudioFileStub;
let validateAudioFiles;
const validMp3File = 'path/to/valid/file.mp3';
const unsupportedFile = 'path/to/unsupported/file.wav';
const invalidFile = 'path/to/invalid/file.txt';
beforeEach(async function() {
validateAudioFileStub = sinon.stub();
validateAudioFileStub.withArgs(validMp3File).resolves(validMp3File);
validateAudioFileStub.withArgs(unsupportedFile).resolves(unsupportedFile);
validateAudioFileStub.withArgs(invalidFile).resolves(null);
// Mocking validateAudioFile and its dependencies
validateAudioFiles = await esmock('../../src/services/fileService.js', {
'../../src/utils/validateAudioFiles.js': { default: validateAudioFileStub }
});
validateAudioFiles = validateAudioFiles.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should return only valid and supported audio file paths', async function() {
const filePaths = [validMp3File, unsupportedFile, invalidFile];
const result = await validateAudioFiles(filePaths);
// Ensure that only the valid mp3 file is returned
expect(result).to.deep.equal([validMp3File]);
});
it('should throw a TypeError if input is not an array', async function() {
try {
await validateAudioFiles('not an array');
expect.fail('Expected TypeError to be thrown');
} catch (error) {
expect(error).to.be.instanceOf(TypeError);
expect(error.message).to.equal('Input must be an array of file paths (strings).');
}
});
it('should ignore unsupported file extensions', async function() {
const filePaths = [validMp3File, unsupportedFile];
const result = await validateAudioFiles(filePaths);
// Ensure that only the valid mp3 file is returned
expect(result).to.deep.equal([validMp3File]);
});
it('should handle empty array input gracefully', async function() {
const result = await validateAudioFiles([]);
// Ensure that an empty array is returned
expect(result).to.deep.equal([]);
});
it('should log an error for unsupported extensions', async function() {
const consoleErrorStub = sinon.stub(console, 'error');
const filePaths = [unsupportedFile];
await validateAudioFiles(filePaths);
// Ensure that an error is logged for the unsupported file
expect(consoleErrorStub.calledOnceWithExactly(`File ${path.basename(unsupportedFile)} is an audio file but .wav format is not yet supported and so is ignored.`)).to.be.true;
consoleErrorStub.restore();
});
});

View File

@@ -0,0 +1,107 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
describe('writeMetadata', function() {
let fsExistsSyncStub;
let writeMetadataPromiseStub;
let writeAlbumArtStub;
let renameFileStub;
let writeMetadata;
const filePath = 'path/to/track.mp3';
const albumArtPath = 'path/to/albumArt.jpg';
const newFilePath = 'path/to/new_track.mp3';
const validMetadata = {
title: 'Test Title',
artist: 'Test Artist',
lyrics: 'Test Lyrics',
albumArt: albumArtPath,
};
beforeEach(async function() {
fsExistsSyncStub = sinon.stub();
writeMetadataPromiseStub = sinon.stub().resolves();
writeAlbumArtStub = sinon.stub().resolves();
renameFileStub = sinon.stub().resolves(newFilePath);
writeMetadata = await esmock('../../../../src/services/metadata/metadataWriters/writeMetadata.js', {
'fs': { existsSync: fsExistsSyncStub },
'util': { promisify: sinon.stub().returns(writeMetadataPromiseStub) },
'../../../../src/services/metadata/metadataWriters/writeAlbumArt.js': { default: writeAlbumArtStub },
'../../../../src/utils/renameAudioFileTitle.js': { default: renameFileStub }
});
writeMetadata = writeMetadata.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should write metadata and album art to an MP3 file', async function() {
fsExistsSyncStub.withArgs(filePath).returns(true);
await writeMetadata(validMetadata, filePath);
expect(writeMetadataPromiseStub.calledOnceWithExactly(filePath, {
title: validMetadata.title,
artist: validMetadata.artist,
lyrics: validMetadata.lyrics,
})).to.be.true;
expect(writeAlbumArtStub.calledOnceWithExactly(filePath, albumArtPath)).to.be.true;
expect(renameFileStub.calledOnceWithExactly(validMetadata.title, validMetadata.artist, filePath)).to.be.true;
});
it('should throw an error if metadata is not an object', async function() {
fsExistsSyncStub.withArgs(filePath).returns(true);
try {
await writeMetadata('invalid metadata', filePath);
expect.fail('Expected TypeError to be thrown');
} catch (error) {
expect(error).to.be.instanceOf(TypeError);
expect(error.message).to.equal('Metadata provided must be an object.');
}
});
it('should throw an error if filePath is not a string', async function() {
fsExistsSyncStub.withArgs(filePath).returns(true);
try {
await writeMetadata(validMetadata, {});
expect.fail('Expected TypeError to be thrown');
} catch (error) {
expect(error).to.be.instanceOf(TypeError);
expect(error.message).to.equal('File path must be a string.');
}
});
it('should throw an error if the file path does not exist', async function() {
fsExistsSyncStub.withArgs(filePath).returns(false);
try {
await writeMetadata(validMetadata, filePath);
expect.fail('Expected Error to be thrown');
} catch (error) {
expect(error.message).to.equal(`No file found at the given path: ${filePath}`);
}
});
it('should handle errors during metadata writing process and provide context', async function() {
const writeError = new Error('Failed to write metadata');
fsExistsSyncStub.withArgs(filePath).returns(true);
writeMetadataPromiseStub.rejects(writeError);
try {
await writeMetadata(validMetadata, filePath);
expect.fail('Expected Error to be thrown');
} catch (error) {
expect(error.message).to.equal(`Failed to write metadata to ${filePath}: ${writeError.message}`);
}
});
});

View File

@@ -0,0 +1,75 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
describe('writeMetadataService', function() {
let writeMetadataStub;
let writeMetadataService;
const validMetadata = {
title: 'Test Title',
artist: 'Test Artist',
lyrics: 'Test Lyrics',
filePath: 'path/to/test/file.mp3',
};
const invalidMetadata = {
title: 'Test Title',
artist: 'Test Artist',
lyrics: 'Test Lyrics',
filePath: '', // Invalid file path
};
beforeEach(async function() {
writeMetadataStub = sinon.stub().resolves();
// Mocking writeMetadata and its dependencies
writeMetadataService = await esmock('../../../src/services/metadata/metadataWritingService.js', {
'../../../src/services/metadata/metadataWriters/writeMetadata.js': { default: writeMetadataStub }
});
writeMetadataService = writeMetadataService.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should write metadata to all files in the array', async function() {
const metadataArray = [validMetadata, validMetadata];
await writeMetadataService(metadataArray);
// Ensure writeMetadata is called twice
expect(writeMetadataStub.calledTwice).to.be.true;
expect(writeMetadataStub.alwaysCalledWith(validMetadata, validMetadata.filePath)).to.be.true;
});
it('should skip metadata objects without a file path', async function() {
const metadataArray = [validMetadata, invalidMetadata, { title: 'No FilePath' }];
await writeMetadataService(metadataArray);
// Ensure writeMetadata is called only once for the valid metadata
expect(writeMetadataStub.calledOnceWithExactly(validMetadata, validMetadata.filePath)).to.be.true;
});
it('should handle errors during the writing of metadata', async function() {
const error = new Error('Failed to write metadata');
writeMetadataStub.rejects(error);
const metadataArray = [validMetadata];
try {
await writeMetadataService(metadataArray);
} catch (e) {
expect.fail('No error should be thrown from service');
}
// Ensure error is logged (this would normally go to your logging mechanism)
expect(writeMetadataStub.calledOnceWithExactly(validMetadata, validMetadata.filePath)).to.be.true;
// Check if console.error was called (mock console.error if needed)
});
});

View File

@@ -0,0 +1,60 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
describe('normalizeMetadata', function() {
let normalizeMusicBrainzStub;
let normalizeMetadata;
const fileObjectsWithMetadata = [{ id: 1 }, { id: 2 }];
beforeEach(async function() {
normalizeMusicBrainzStub = sinon.stub().resolves([{ normalized: true }]);
// Mocking normalizeMusicBrainz module and its dependencies
normalizeMetadata = await esmock('../../../../src/services/metadata/normalizers/metadataNormalizerService.js', {
'../../../../src/services/metadata/normalizers/normalizeMusicBrainz.js': { default: normalizeMusicBrainzStub }
});
normalizeMetadata = normalizeMetadata.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should use normalizeMusicBrainz for "acoustid" source', async function() {
const result = await normalizeMetadata(fileObjectsWithMetadata, 'acoustid');
// Ensure normalizeMusicBrainzStub is called once with correct parameters
expect(normalizeMusicBrainzStub.calledOnceWithExactly(fileObjectsWithMetadata)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ normalized: true }]);
});
it('should use default normalizer (normalizeMusicBrainz) for unsupported source', async function() {
const result = await normalizeMetadata(fileObjectsWithMetadata, 'unsupportedSource');
// Ensure default normalizer is used for unsupported source
expect(normalizeMusicBrainzStub.calledOnceWithExactly(fileObjectsWithMetadata)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ normalized: true }]);
});
it('should use default normalizer (normalizeMusicBrainz) when source is not provided', async function() {
const result = await normalizeMetadata(fileObjectsWithMetadata);
// Ensure default normalizer is used when source is not provided
expect(normalizeMusicBrainzStub.calledOnceWithExactly(fileObjectsWithMetadata)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ normalized: true }]);
});
// it('should return <audd normalizer> when "audd" source is provided', async function() {
//
// const result = await normalizeMetadata(fileObjectsWithMetadata, 'audd');
//
// expect(normalizeMusicBrainzStub.called).to.be.false;
// });
});

View File

@@ -0,0 +1,113 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
describe('normalizeMusicBrainz', function() {
let saveImageToFileStub;
let normalizeMusicBrainz;
beforeEach(async function() {
saveImageToFileStub = sinon.stub().resolves('/path/to/saved/image.jpg');
// Mocking saveImageToFile and its dependencies
normalizeMusicBrainz = await esmock('../../../../src/services/metadata/normalizers/normalizeMusicBrainz.js', {
'../../../../src/services/saveImageToFile.js': { default: saveImageToFileStub }
});
normalizeMusicBrainz = normalizeMusicBrainz.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should normalize metadata and save album art', async function() {
const fileObjectsWithMetadata = [{
trackMetadata: {
title: 'Test Title',
'artist-credit': [{ artist: { name: 'Test Artist' } }],
'first-release-date': '2023-01-01',
},
albumArtUrl: 'http://example.com/albumArt.jpg',
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}];
const result = await normalizeMusicBrainz(fileObjectsWithMetadata);
expect(result).to.deep.equal([{
title: 'Test Title',
artist: 'Test Artist',
releaseDate: '2023-01-01',
albumArt: '/path/to/saved/image.jpg',
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}]);
expect(saveImageToFileStub.calledOnceWithExactly('http://example.com/albumArt.jpg', './images')).to.be.true;
});
it('should handle missing metadata and return null', async function() {
const fileObjectsWithMetadata = [{
trackMetadata: null,
albumArtUrl: 'http://example.com/albumArt.jpg',
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}];
const result = await normalizeMusicBrainz(fileObjectsWithMetadata);
expect(result).to.deep.equal([null]);
expect(saveImageToFileStub.called).to.be.false;
});
it('should handle missing album art URL gracefully', async function() {
const fileObjectsWithMetadata = [{
trackMetadata: {
title: 'Test Title',
'artist-credit': [{ artist: { name: 'Test Artist' } }],
'first-release-date': '2023-01-01',
},
albumArtUrl: null,
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}];
const result = await normalizeMusicBrainz(fileObjectsWithMetadata);
expect(result).to.deep.equal([{
title: 'Test Title',
artist: 'Test Artist',
releaseDate: '2023-01-01',
albumArt: null,
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}]);
expect(saveImageToFileStub.called).to.be.false;
});
it('should propagate errors from saveImageToFile', async function() {
const fileObjectsWithMetadata = [{
trackMetadata: {
title: 'Test Title',
'artist-credit': [{ artist: { name: 'Test Artist' } }],
'first-release-date': '2023-01-01',
},
albumArtUrl: 'http://example.com/albumArt.jpg',
lyrics: 'Test lyrics',
filePath: 'path/to/test/file.mp3',
}];
const saveImageError = new Error('Failed to save image');
saveImageToFileStub.rejects(saveImageError);
try {
await normalizeMusicBrainz(fileObjectsWithMetadata);
expect.fail('Expected an error to be thrown');
} catch (error) {
expect(error.message).to.equal(`Error normalizing MusicBrainz metadata for file path/to/test/file.mp3: ${saveImageError.message}`);
}
expect(saveImageToFileStub.calledOnceWithExactly('http://example.com/albumArt.jpg', './images')).to.be.true;
});
});

View File

@@ -0,0 +1,66 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
describe('recognizeAudio', function() {
let recognizeUsingAuddStub;
let recognizeUsingAcoustidStub;
let recognizeAudio;
const filePaths = ['path/to/file1.mp3', 'path/to/file2.mp3'];
beforeEach(async function() {
recognizeUsingAuddStub = sinon.stub().resolves([{ recognized: true, service: 'audd' }]);
recognizeUsingAcoustidStub = sinon.stub().resolves([{ recognized: true, service: 'acoustid' }]);
// Mocking recognition services and their dependencies
recognizeAudio = await esmock('../../src/services/musicRecognitionService.js', {
'../../src/adapters/recognition/auddAdapter.js': { default: recognizeUsingAuddStub },
'../../src/adapters/recognition/acoustidAdapter.js': { default: recognizeUsingAcoustidStub }
});
recognizeAudio = recognizeAudio.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should use recognizeUsingAudd for "audd" source', async function() {
const result = await recognizeAudio(filePaths, 'audd');
// Ensure recognizeUsingAuddStub is called once with the correct parameters
expect(recognizeUsingAuddStub.calledOnceWithExactly(filePaths)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ recognized: true, service: 'audd' }]);
});
it('should use recognizeUsingAcoustid for "acoustid" source', async function() {
const result = await recognizeAudio(filePaths, 'acoustid');
// Ensure recognizeUsingAcoustidStub is called once with the correct parameters
expect(recognizeUsingAcoustidStub.calledOnceWithExactly(filePaths)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ recognized: true, service: 'acoustid' }]);
});
it('should use default recognition service for unsupported source', async function() {
const result = await recognizeAudio(filePaths, 'unsupportedSource');
// Ensure default recognition service (recognizeUsingAcoustid) is used for unsupported source
expect(recognizeUsingAcoustidStub.calledOnceWithExactly(filePaths)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ recognized: true, service: 'acoustid' }]);
});
it('should use default recognition service when source is not provided', async function() {
const result = await recognizeAudio(filePaths);
// Ensure default recognition service (recognizeUsingAcoustid) is used when source is not provided
expect(recognizeUsingAcoustidStub.calledOnceWithExactly(filePaths)).to.be.true;
// Validate the return value
expect(result).to.deep.equal([{ recognized: true, service: 'acoustid' }]);
});
});

View File

@@ -0,0 +1,72 @@
import { expect } from 'chai';
import sinon from 'sinon';
import esmock from 'esmock';
import path from 'path';
describe('saveImageToFile', function() {
let downloadImageStub;
let ensureDirectoryExistsStub;
let saveImageToFile;
const imageUrl = 'http://example.com/image.jpg';
const outputDirectory = 'path/to/output';
const savedImagePath = path.join(path.resolve(outputDirectory), path.basename(imageUrl));
beforeEach(async function() {
downloadImageStub = sinon.stub().resolves(savedImagePath);
ensureDirectoryExistsStub = sinon.stub();
// Mocking downloadImage and ensureDirectoryExists functions
saveImageToFile = await esmock('../../src/services/saveImageToFile.js', {
'../../src/utils/downloadImage.js': { default: downloadImageStub },
'../../src/utils/ensureDirectoryExists.js': { default: ensureDirectoryExistsStub }
});
saveImageToFile = saveImageToFile.default;
});
afterEach(() => {
sinon.restore();
esmock.purge();
});
it('should save image to specified directory', async function() {
const result = await saveImageToFile(imageUrl, outputDirectory);
// Ensure ensureDirectoryExists is called with the resolved output directory
expect(ensureDirectoryExistsStub.calledOnceWithExactly(path.resolve(outputDirectory))).to.be.true;
// Ensure downloadImage is called with correct URL and save path
expect(downloadImageStub.calledOnceWithExactly(imageUrl, savedImagePath)).to.be.true;
// Validate the return value (saved image path)
expect(result).to.equal(savedImagePath);
});
it('should throw an error if downloadImage fails', async function() {
const error = new Error('Failed to download image');
downloadImageStub.rejects(error);
try {
await saveImageToFile(imageUrl, outputDirectory);
expect.fail('Expected an error to be thrown');
} catch (err) {
// Ensure error is logged and re-thrown
expect(err).to.equal(error);
}
});
it('should throw an error if ensureDirectoryExists fails', async function() {
const error = new Error('Failed to ensure directory exists');
ensureDirectoryExistsStub.throws(error);
try {
await saveImageToFile(imageUrl, outputDirectory);
expect.fail('Expected an error to be thrown');
} catch (err) {
// Ensure error is logged and re-thrown
expect(err).to.equal(error);
}
});
});