fix(Spotify Node): Fix issue with null values breaking the response (#12080)

This commit is contained in:
Jon 2025-01-09 14:42:27 +00:00 committed by GitHub
parent 1d86c4fdd2
commit a56a46259d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 7223 additions and 4 deletions

View file

@ -5,7 +5,7 @@ import type {
IHookFunctions,
JsonObject,
IHttpRequestMethods,
IRequestOptions,
IHttpRequestOptions,
} from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
@ -21,7 +21,7 @@ export async function spotifyApiRequest(
query?: IDataObject,
uri?: string,
): Promise<any> {
const options: IRequestOptions = {
const options: IHttpRequestOptions = {
method,
headers: {
'User-Agent': 'n8n',
@ -29,7 +29,7 @@ export async function spotifyApiRequest(
Accept: ' application/json',
},
qs: query,
uri: uri || `https://api.spotify.com/v1${endpoint}`,
url: uri ?? `https://api.spotify.com/v1${endpoint}`,
json: true,
};
@ -37,7 +37,7 @@ export async function spotifyApiRequest(
options.body = body;
}
try {
return await this.helpers.requestOAuth2.call(this, 'spotifyOAuth2Api', options);
return await this.helpers.httpRequestWithAuthentication.call(this, 'spotifyOAuth2Api', options);
} catch (error) {
throw new NodeApiError(this.getNode(), error as JsonObject);
}

View file

@ -1308,6 +1308,10 @@ export class Spotify implements INodeType {
);
}
// Remove null values from the response
if (operation === 'getUserPlaylists') {
responseData = responseData.filter((item: IDataObject) => item !== null);
}
const executionData = this.helpers.constructExecutionMetaData(
this.helpers.returnJsonArray(responseData as IDataObject[]),
{ itemData: { item: i } },

View file

@ -0,0 +1,62 @@
import type { IExecuteFunctions, IHookFunctions } from 'n8n-workflow';
import { NodeApiError } from 'n8n-workflow';
import { spotifyApiRequest } from '../GenericFunctions';
describe('Spotify -> GenericFunctions', () => {
let mockThis: IHookFunctions | IExecuteFunctions;
beforeEach(() => {
mockThis = {
helpers: {
httpRequestWithAuthentication: jest.fn(),
},
getNode: jest.fn().mockReturnValue({}),
} as unknown as IHookFunctions | IExecuteFunctions;
});
it('should make a request with the correct options', async () => {
const method = 'GET';
const endpoint = '/me';
const body = {};
const query = { limit: 10 };
const response = { data: 'test' };
(mockThis.helpers.httpRequestWithAuthentication as jest.Mock).mockResolvedValue(response);
const result = await spotifyApiRequest.call(mockThis, method, endpoint, body, query);
expect(mockThis.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith(
'spotifyOAuth2Api',
{
method,
headers: {
'User-Agent': 'n8n',
'Content-Type': 'text/plain',
Accept: ' application/json',
},
qs: query,
url: `https://api.spotify.com/v1${endpoint}`,
json: true,
},
);
expect(result).toEqual(response);
});
it('should throw a NodeApiError on request failure', async () => {
const method = 'GET';
const endpoint = '/me';
const body = {};
const query = { limit: 10 };
const error = new Error('Request failed');
(mockThis.helpers.httpRequestWithAuthentication as jest.Mock).mockRejectedValue(error);
await expect(spotifyApiRequest.call(mockThis, method, endpoint, body, query)).rejects.toThrow(
NodeApiError,
);
expect(mockThis.getNode).toHaveBeenCalled();
});
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,46 @@
import nock from 'nock';
import {
getAlbum,
getAlbumTracks,
getArtist,
getNewReleases,
searchForAlbum,
} from './apiResponses';
import {
setup,
equalityTest,
workflowToTests,
getWorkflowFilenames,
} from '../../../../test/nodes/Helpers';
describe('Spotify', () => {
describe('Run workflow', () => {
const workflows = getWorkflowFilenames(__dirname);
const tests = workflowToTests(workflows);
beforeAll(() => {
nock.disableNetConnect();
const mock = nock('https://api.spotify.com/v1');
mock
.get('/search')
.query({ q: 'From Xero', type: 'album', limit: 2 })
.reply(200, searchForAlbum);
mock.get('/browse/new-releases').query({ limit: 2 }).reply(200, getNewReleases);
mock.get('/albums/4R6FV9NSzhPihHR0h4pI93/tracks').reply(200, getAlbumTracks);
mock.get('/albums/4R6FV9NSzhPihHR0h4pI93').reply(200, getAlbum);
mock.get('/artists/12Chz98pHFMPJEknJQMWvI').reply(200, getArtist);
});
afterAll(() => {
nock.restore();
});
const nodeTypes = setup(tests);
for (const testData of tests) {
test(testData.description, async () => await equalityTest(testData, nodeTypes));
}
});
});

View file

@ -187,4 +187,24 @@ BQIDAQAB
expires_in: 86400,
},
},
spotifyOAuth2Api: {
accessTokenUrl: 'https://accounts.spotify.com/api/token',
authQueryParameters: '',
authUrl: 'https://accounts.spotify.com/authorize',
authentication: 'header',
clientId: 'CLIENT_ID',
clientSecret: 'CLIENT_SECRET',
grantType: 'authorizationCode',
oauthTokenData: {
access_token: 'ACCESS_TOKEN',
expires_in: 3600,
refresh_token: 'REFRESH_TOKEN',
scope:
'playlist-read-private playlist-read-collaborative user-modify-playback-state user-library-read user-follow-read playlist-modify-private playlist-modify-public user-read-playback-state user-read-currently-playing user-read-recently-played',
token_type: 'Bearer',
},
scope:
'user-read-playback-state playlist-read-collaborative user-modify-playback-state playlist-modify-public user-read-currently-playing playlist-read-private user-read-recently-played playlist-modify-private user-library-read user-follow-read',
server: 'https://api.spotify.com/',
},
} as const;