/**
* Unit tests for analyze_loudness tool
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { analyzeLoudnessTool, handleAnalyzeLoudness } from '../../../src/tools/analyze-loudness.js';
// Mock the http module
vi.mock('../../../src/utils/http.js', () => ({
httpPost: vi.fn(),
buildApiUrl: vi.fn((endpoint) => `https://api.ircamamplify.io/v1${endpoint}`),
}));
// Mock auth
vi.mock('../../../src/utils/auth.js', () => ({
getApiKey: vi.fn(() => 'test-api-key'),
hasApiKey: vi.fn(() => true),
createAuthHeaders: vi.fn(() => ({
Authorization: 'Bearer test-api-key',
'Content-Type': 'application/json',
})),
API_KEY_ENV_VAR: 'IRCAM_AMPLIFY_API_KEY',
}));
import { httpPost } from '../../../src/utils/http.js';
describe('analyze_loudness tool', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
describe('tool definition', () => {
it('should have correct name', () => {
expect(analyzeLoudnessTool.name).toBe('analyze_loudness');
});
it('should have description about loudness analysis', () => {
expect(analyzeLoudnessTool.description).toBeTruthy();
expect(analyzeLoudnessTool.description).toContain('LUFS');
expect(analyzeLoudnessTool.description).toContain('peak');
});
it('should require audio_url parameter', () => {
expect(analyzeLoudnessTool.inputSchema).toBeDefined();
expect(analyzeLoudnessTool.inputSchema.required).toContain('audio_url');
});
});
describe('handleAnalyzeLoudness', () => {
it('should return loudness metrics on success', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
integrated: -14.5,
true_peak: -1.2,
range: 8.3,
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
const result = await handleAnalyzeLoudness({
audio_url: 'https://example.com/song.mp3',
});
expect(result).toEqual({
integrated_lufs: -14.5,
true_peak_db: -1.2,
loudness_range: 8.3,
});
});
it('should handle typical streaming loudness values', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
integrated: -14.0,
true_peak: -1.0,
range: 6.0,
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
const result = await handleAnalyzeLoudness({
audio_url: 'https://example.com/spotify-track.mp3',
});
expect(result.integrated_lufs).toBe(-14.0);
expect(result.true_peak_db).toBe(-1.0);
expect(result.loudness_range).toBe(6.0);
});
it('should handle quiet audio', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
integrated: -23.0,
true_peak: -12.0,
range: 15.0,
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
const result = await handleAnalyzeLoudness({
audio_url: 'https://example.com/classical.mp3',
});
expect(result.integrated_lufs).toBeLessThan(-20);
expect(result.loudness_range).toBeGreaterThan(10);
});
it('should throw error for invalid URL', async () => {
await expect(
handleAnalyzeLoudness({ audio_url: '' })
).rejects.toMatchObject({
code: 'INVALID_URL',
});
});
it('should throw error when API returns error', async () => {
const mockError = {
ok: false,
status: 429,
error: {
code: 'RATE_LIMITED',
message: 'Rate limited',
suggestion: 'Wait and retry',
retry_after: 30,
},
};
vi.mocked(httpPost).mockResolvedValue(mockError);
await expect(
handleAnalyzeLoudness({ audio_url: 'https://example.com/song.mp3' })
).rejects.toMatchObject({
code: 'RATE_LIMITED',
});
});
it('should call API with audio URL in body', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
integrated: -14.0,
true_peak: -1.0,
range: 6.0,
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
await handleAnalyzeLoudness({
audio_url: 'https://example.com/test.mp3',
});
// Verify httpPost was called with audio URL in body
expect(httpPost).toHaveBeenCalled();
const calls = vi.mocked(httpPost).mock.calls;
expect(calls[0][1]).toEqual({ url: 'https://example.com/test.mp3' });
});
});
});