/**
* Unit tests for analyze_music tool
*/
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { analyzeMusicTool, handleAnalyzeMusic } from '../../../src/tools/analyze-music.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_music tool', () => {
beforeEach(() => {
vi.clearAllMocks();
});
afterEach(() => {
vi.resetAllMocks();
});
describe('tool definition', () => {
it('should have correct name', () => {
expect(analyzeMusicTool.name).toBe('analyze_music');
});
it('should have description', () => {
expect(analyzeMusicTool.description).toBeTruthy();
expect(analyzeMusicTool.description).toContain('genre');
expect(analyzeMusicTool.description).toContain('mood');
expect(analyzeMusicTool.description).toContain('tempo');
});
it('should require audio_url parameter', () => {
expect(analyzeMusicTool.inputSchema).toBeDefined();
expect(analyzeMusicTool.inputSchema.required).toContain('audio_url');
});
});
describe('handleAnalyzeMusic', () => {
it('should return music analysis result on success', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
tags: {
genre: ['electronic', 'house'],
mood: ['energetic', 'uplifting'],
instruments: ['synthesizer', 'drums'],
},
analysis: {
bpm: 128,
key: 'A minor',
},
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
const result = await handleAnalyzeMusic({
audio_url: 'https://example.com/song.mp3',
});
expect(result).toEqual({
genre: ['electronic', 'house'],
mood: ['energetic', 'uplifting'],
tempo: 128,
key: 'A minor',
instruments: ['synthesizer', 'drums'],
});
});
it('should throw error for invalid URL', async () => {
await expect(
handleAnalyzeMusic({ audio_url: '' })
).rejects.toMatchObject({
code: 'INVALID_URL',
});
});
it('should throw error for non-HTTP URL', async () => {
await expect(
handleAnalyzeMusic({ audio_url: 'ftp://example.com/song.mp3' })
).rejects.toMatchObject({
code: 'INVALID_URL',
});
});
it('should throw error when API returns error', async () => {
const mockError = {
ok: false,
status: 401,
error: {
code: 'INVALID_API_KEY',
message: 'Invalid API key',
suggestion: 'Check your API key',
},
};
vi.mocked(httpPost).mockResolvedValue(mockError);
await expect(
handleAnalyzeMusic({ audio_url: 'https://example.com/song.mp3' })
).rejects.toMatchObject({
code: 'INVALID_API_KEY',
});
});
it('should call API with audio URL in body', async () => {
const mockResponse = {
ok: true,
status: 200,
data: {
result: {
tags: {
genre: ['pop'],
mood: ['happy'],
instruments: ['guitar'],
},
analysis: {
bpm: 120,
key: 'C major',
},
},
},
};
vi.mocked(httpPost).mockResolvedValue(mockResponse);
await handleAnalyzeMusic({
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' });
});
});
});