Fixed requests

This commit is contained in:
Adina Totorean 2025-02-10 11:24:12 +02:00
parent 9801c907fc
commit d210fcc172
9 changed files with 340 additions and 82 deletions

View file

@ -75,15 +75,22 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
requestOptions.headers['x-ms-session-token'] = credentials.sessionToken; requestOptions.headers['x-ms-session-token'] = credentials.sessionToken;
} }
// const url = new URL (requestOptions.uri); let url;
const url = new URL(requestOptions.baseURL + requestOptions.url); if (requestOptions.url) {
const pathSegments = url.pathname.split('/').filter((segment) => segment); url = new URL(requestOptions.baseURL + requestOptions.url);
//@ts-ignore
} else if (requestOptions.uri) {
//@ts-ignore
url = new URL(requestOptions.uri);
}
const pathSegments = url?.pathname.split('/').filter((segment) => segment);
let resourceType = ''; let resourceType = '';
let resourceId = ''; let resourceId = '';
if (pathSegments.includes('docs')) { if (pathSegments?.includes('docs')) {
const docsIndex = pathSegments.lastIndexOf('docs'); const docsIndex = pathSegments.lastIndexOf('docs');
resourceType = 'docs'; resourceType = 'docs';
if (pathSegments[docsIndex + 1]) { if (pathSegments[docsIndex + 1]) {
@ -92,7 +99,7 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
} else { } else {
resourceId = pathSegments.slice(0, docsIndex).join('/'); resourceId = pathSegments.slice(0, docsIndex).join('/');
} }
} else if (pathSegments.includes('colls')) { } else if (pathSegments?.includes('colls')) {
const collsIndex = pathSegments.lastIndexOf('colls'); const collsIndex = pathSegments.lastIndexOf('colls');
resourceType = 'colls'; resourceType = 'colls';
if (pathSegments[collsIndex + 1]) { if (pathSegments[collsIndex + 1]) {
@ -101,7 +108,7 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
} else { } else {
resourceId = pathSegments.slice(0, collsIndex).join('/'); resourceId = pathSegments.slice(0, collsIndex).join('/');
} }
} else if (pathSegments.includes('dbs')) { } else if (pathSegments?.includes('dbs')) {
const dbsIndex = pathSegments.lastIndexOf('dbs'); const dbsIndex = pathSegments.lastIndexOf('dbs');
resourceType = 'dbs'; resourceType = 'dbs';
resourceId = pathSegments.slice(0, dbsIndex + 2).join('/'); resourceId = pathSegments.slice(0, dbsIndex + 2).join('/');

View file

@ -1,3 +1,7 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import type { import type {
DeclarativeRestApiSettings, DeclarativeRestApiSettings,
@ -71,6 +75,7 @@ export async function microsoftCosmosDbRequest(
const requestOptions: IHttpRequestOptions = { const requestOptions: IHttpRequestOptions = {
...opts, ...opts,
// eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
baseURL: `${credentials.baseUrl}`, baseURL: `${credentials.baseUrl}`,
headers: { headers: {
...opts.headers, ...opts.headers,
@ -99,8 +104,6 @@ export async function microsoftCosmosDbRequest(
}; };
try { try {
console.log('Final Request Options before Request:', requestOptions);
return (await this.helpers.requestWithAuthentication.call( return (await this.helpers.requestWithAuthentication.call(
this, this,
'microsoftCosmosDbSharedKeyApi', 'microsoftCosmosDbSharedKeyApi',
@ -442,14 +445,15 @@ export async function validateOperations(
); );
} }
//To-Do-check to not send properties it doesn't need
return { return {
op: operation.op, op: operation.op,
path: operation.op === 'move' ? operation.toPath?.value : operation.path?.value, path: operation.op === 'move' ? operation.toPath?.value : operation.path?.value,
...(operation.from ? { from: operation.from.value } : {}), ...(operation.from ? { from: operation.from.value } : {}),
...(operation.op === 'incr' ...(operation.op === 'incr'
? { value: Number(operation.value) } ? { value: Number(operation.value) }
: { value: isNaN(Number(operation.value)) ? operation.value : Number(operation.value) }), : operation.value !== undefined
? { value: isNaN(Number(operation.value)) ? operation.value : Number(operation.value) }
: {}),
}; };
}); });
@ -458,12 +462,11 @@ export async function validateOperations(
return requestOptions; return requestOptions;
} }
export async function validateFields( export async function validateContainerFields(
this: IExecuteSingleFunctions, this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions, requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> { ): Promise<IHttpRequestOptions> {
const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject; const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject;
const indexingPolicy = additionalFields.indexingPolicy;
const manualThroughput = additionalFields.offerThroughput; const manualThroughput = additionalFields.offerThroughput;
const autoscaleThroughput = additionalFields.maxThroughput; const autoscaleThroughput = additionalFields.maxThroughput;
@ -473,32 +476,28 @@ export async function validateFields(
{}, {},
{ {
message: 'Bad parameter', message: 'Bad parameter',
description: description: 'Please choose only one of Max RU/s (Autoscale) and Manual Throughput RU/s',
'Please choose only one of Max RU/s (Autoscale) and Max RU/s (Manual Throughput)',
}, },
); );
} }
if (autoscaleThroughput && requestOptions?.qs) {
requestOptions.qs['x-ms-cosmos-offer-autopilot-settings'] = { if (autoscaleThroughput) {
maxThroughput: autoscaleThroughput, requestOptions.headers = {
...requestOptions.headers,
'x-ms-cosmos-offer-autopilot-setting': { maxThroughput: autoscaleThroughput },
}; };
} }
if (!indexingPolicy || Object.keys(indexingPolicy).length === 0) { if (manualThroughput) {
throw new NodeApiError( requestOptions.headers = {
this.getNode(), ...requestOptions.headers,
{}, 'x-ms-offer-throughput': manualThroughput,
{ };
message: 'Invalid Indexing Policy',
description: 'Please provide a valid indexingPolicy JSON.',
},
);
} }
return requestOptions; return requestOptions;
} }
//WIP
export async function handlePagination( export async function handlePagination(
this: IExecutePaginationFunctions, this: IExecutePaginationFunctions,
resultOptions: DeclarativeRestApiSettings.ResultOptions, resultOptions: DeclarativeRestApiSettings.ResultOptions,
@ -533,7 +532,6 @@ export async function handlePagination(
} }
} }
//TO-DO-check-if-works
if (responseData.length > 0) { if (responseData.length > 0) {
const lastItem = responseData[responseData.length - 1]; const lastItem = responseData[responseData.length - 1];
@ -554,40 +552,34 @@ export async function handlePagination(
return aggregatedResult.map((result) => ({ json: result })); return aggregatedResult.map((result) => ({ json: result }));
} }
//WIP
export async function handleErrorPostReceive( export async function handleErrorPostReceive(
this: IExecuteSingleFunctions, this: IExecuteSingleFunctions,
data: INodeExecutionData[], data: INodeExecutionData[],
response: IN8nHttpFullResponse, response: IN8nHttpFullResponse,
): Promise<INodeExecutionData[]> { ): Promise<INodeExecutionData[]> {
console.log('Status code❌', response.statusCode);
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) { if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
const responseBody = response.body as IDataObject; const responseBody = response.body as IDataObject;
console.log('Got here ❌', responseBody);
let errorMessage = 'Unknown error occurred'; let errorMessage = 'Unknown error occurred';
let errorDescription = 'An unexpected error was encountered.';
if (typeof responseBody === 'object' && responseBody !== null) {
if (typeof responseBody.code === 'string') {
errorMessage = responseBody.code;
}
if (typeof responseBody.message === 'string') { if (typeof responseBody.message === 'string') {
try { errorDescription = responseBody.message;
const jsonMatch = responseBody.message.match(/Message: (\{.*\})/);
if (jsonMatch && jsonMatch[1]) {
const parsedMessage = JSON.parse(jsonMatch[1]);
if (
parsedMessage.Errors &&
Array.isArray(parsedMessage.Errors) &&
parsedMessage.Errors.length > 0
) {
errorMessage = parsedMessage.Errors[0].split(' Learn more:')[0].trim();
}
}
} catch (error) {
errorMessage = 'Failed to extract error message';
} }
} }
throw new ApplicationError(errorMessage); throw new NodeApiError(
this.getNode(),
{},
{
message: errorMessage,
description: errorDescription,
},
);
} }
return data; return data;
} }
@ -675,7 +667,7 @@ export async function searchItems(
}; };
} }
function extractFieldPaths(obj: any, prefix = ''): string[] { function extractFieldPaths(obj: IDataObject, prefix = ''): string[] {
let paths: string[] = []; let paths: string[] = [];
Object.entries(obj).forEach(([key, value]) => { Object.entries(obj).forEach(([key, value]) => {
@ -692,7 +684,7 @@ function extractFieldPaths(obj: any, prefix = ''): string[] {
} }
}); });
} else if (typeof value === 'object' && value !== null) { } else if (typeof value === 'object' && value !== null) {
paths = paths.concat(extractFieldPaths(value, newPath)); paths = paths.concat(extractFieldPaths(value as IDataObject, newPath));
} else { } else {
paths.push(newPath); paths.push(newPath);
} }
@ -783,6 +775,34 @@ export async function getProperties(this: ILoadOptionsFunctions): Promise<INodeL
}; };
} }
export async function presendLimitField(
this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions,
): Promise<IHttpRequestOptions> {
const returnAll = this.getNodeParameter('returnAll');
let limit;
if (!returnAll) {
limit = this.getNodeParameter('limit');
if (!limit) {
throw new NodeApiError(
this.getNode(),
{},
{
message: 'Limit value not found',
description:
' Please provide a value for "Limit" or set "Return All" to true to return all results',
},
);
}
}
requestOptions.headers = {
...requestOptions.headers,
'x-ms-max-item-count': limit,
};
return requestOptions;
}
export async function formatCustomProperties( export async function formatCustomProperties(
this: IExecuteSingleFunctions, this: IExecuteSingleFunctions,
requestOptions: IHttpRequestOptions, requestOptions: IHttpRequestOptions,
@ -803,7 +823,6 @@ export async function formatCustomProperties(
let parsedProperties: Record<string, unknown>; let parsedProperties: Record<string, unknown>;
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedProperties = JSON.parse(rawCustomProperties); parsedProperties = JSON.parse(rawCustomProperties);
} catch (error) { } catch (error) {
throw new NodeApiError( throw new NodeApiError(
@ -841,11 +860,9 @@ export async function formatJSONFields(
let parsedIndexPolicy: Record<string, unknown> | undefined; let parsedIndexPolicy: Record<string, unknown> | undefined;
try { try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedPartitionKey = JSON.parse(rawPartitionKey); parsedPartitionKey = JSON.parse(rawPartitionKey);
if (indexingPolicy) { if (indexingPolicy) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
parsedIndexPolicy = JSON.parse(indexingPolicy); parsedIndexPolicy = JSON.parse(indexingPolicy);
} }
} catch (error) { } catch (error) {
@ -880,32 +897,32 @@ export async function processResponseItems(
this: IExecuteSingleFunctions, this: IExecuteSingleFunctions,
items: INodeExecutionData[], items: INodeExecutionData[],
response: IN8nHttpFullResponse, response: IN8nHttpFullResponse,
): Promise<any> { ): Promise<INodeExecutionData[]> {
if (!response || typeof response !== 'object' || !Array.isArray(items)) { if (!response || typeof response !== 'object' || !Array.isArray(items)) {
throw new ApplicationError('Invalid response format from Cosmos DB.'); throw new ApplicationError('Invalid response format from Cosmos DB.');
} }
const extractedDocuments: IDataObject[] = items.flatMap((item) => { const extractedDocuments: INodeExecutionData[] = items.flatMap((item) => {
if ( if (
item.json && item.json &&
typeof item.json === 'object' && typeof item.json === 'object' &&
'Documents' in item.json && 'Documents' in item.json &&
Array.isArray(item.json.Documents) Array.isArray(item.json.Documents)
) { ) {
return item.json.Documents as IDataObject[]; return item.json.Documents.map((doc) => ({ json: doc }));
} }
return []; return [];
}); });
return extractedDocuments; return extractedDocuments.length ? extractedDocuments : [{ json: {} }];
} }
export async function processResponseContainers( export async function processResponseContainers(
this: IExecuteSingleFunctions, this: IExecuteSingleFunctions,
items: INodeExecutionData[], items: INodeExecutionData[],
response: IN8nHttpFullResponse, response: IN8nHttpFullResponse,
): Promise<any> { ): Promise<INodeExecutionData[]> {
if (!response || typeof response !== 'object' || !Array.isArray(items)) { if (!response || typeof response !== 'object' || !Array.isArray(items)) {
throw new ApplicationError('Invalid response format from Cosmos DB.'); throw new ApplicationError('Invalid response format from Cosmos DB.');
} }

View file

@ -1,6 +1,11 @@
import type { INodeProperties } from 'n8n-workflow'; import type { INodeProperties } from 'n8n-workflow';
import { formatJSONFields, processResponseContainers, validateFields } from '../GenericFunctions'; import {
formatJSONFields,
handleErrorPostReceive,
processResponseContainers,
validateContainerFields,
} from '../GenericFunctions';
export const containerOperations: INodeProperties[] = [ export const containerOperations: INodeProperties[] = [
{ {
@ -20,13 +25,16 @@ export const containerOperations: INodeProperties[] = [
description: 'Create a container', description: 'Create a container',
routing: { routing: {
send: { send: {
preSend: [formatJSONFields, validateFields], preSend: [formatJSONFields, validateContainerFields],
}, },
request: { request: {
ignoreHttpStatusErrors: true, ignoreHttpStatusErrors: true,
method: 'POST', method: 'POST',
url: '/colls', url: '/colls',
}, },
output: {
postReceive: [handleErrorPostReceive],
},
}, },
action: 'Create container', action: 'Create container',
}, },
@ -42,6 +50,7 @@ export const containerOperations: INodeProperties[] = [
}, },
output: { output: {
postReceive: [ postReceive: [
handleErrorPostReceive,
{ {
type: 'set', type: 'set',
properties: { properties: {
@ -63,6 +72,9 @@ export const containerOperations: INodeProperties[] = [
method: 'GET', method: 'GET',
url: '=/colls/{{ $parameter["collId"] }}', url: '=/colls/{{ $parameter["collId"] }}',
}, },
output: {
postReceive: [handleErrorPostReceive],
},
}, },
action: 'Get container', action: 'Get container',
}, },
@ -77,7 +89,7 @@ export const containerOperations: INodeProperties[] = [
url: '/colls', url: '/colls',
}, },
output: { output: {
postReceive: [processResponseContainers], postReceive: [handleErrorPostReceive, processResponseContainers],
}, },
}, },
action: 'Get many containers', action: 'Get many containers',
@ -156,19 +168,15 @@ export const createFields: INodeProperties[] = [
description: 'The user specified autoscale max RU/s', description: 'The user specified autoscale max RU/s',
}, },
{ {
displayName: 'Max RU/s (for Manual Throughput)', displayName: 'Manual Throughput RU/s',
name: 'offerThroughput', name: 'offerThroughput',
type: 'number', type: 'number',
default: 400, default: 400,
typeOptions: {
minValue: 400,
},
description: description:
'The user specified manual throughput (RU/s) for the collection expressed in units of 100 request units per second', 'The user specified manual throughput (RU/s) for the collection expressed in units of 100 request units per second',
routing: {
send: {
type: 'query',
property: 'x-ms-offer-throughput',
value: '={{$value}}',
},
},
}, },
], ],
placeholder: 'Add Option', placeholder: 'Add Option',

View file

@ -4,6 +4,7 @@ import {
formatCustomProperties, formatCustomProperties,
handleErrorPostReceive, handleErrorPostReceive,
handlePagination, handlePagination,
presendLimitField,
processResponseItems, processResponseItems,
validateOperations, validateOperations,
validatePartitionKey, validatePartitionKey,
@ -97,6 +98,7 @@ export const itemOperations: INodeProperties[] = [
routing: { routing: {
send: { send: {
paginate: true, paginate: true,
preSend: [presendLimitField],
}, },
operations: { operations: {
pagination: handlePagination, pagination: handlePagination,
@ -128,7 +130,7 @@ export const itemOperations: INodeProperties[] = [
}, },
}, },
output: { output: {
postReceive: [handleErrorPostReceive], postReceive: [processResponseItems, handleErrorPostReceive],
}, },
}, },
action: 'Query items', action: 'Query items',
@ -539,13 +541,6 @@ export const getAllFields: INodeProperties[] = [
returnAll: [false], returnAll: [false],
}, },
}, },
routing: {
send: {
property: 'x-ms-max-item-count',
type: 'query',
value: '={{ $value }}',
},
},
type: 'number', type: 'number',
typeOptions: { typeOptions: {
minValue: 1, minValue: 1,
@ -611,7 +606,7 @@ export const queryFields: INodeProperties[] = [
operation: ['query'], operation: ['query'],
}, },
}, },
placeholder: 'SELECT * FROM c WHERE c.name = @name', placeholder: 'SELECT * FROM c WHERE c.name = @Name',
routing: { routing: {
send: { send: {
type: 'body', type: 'body',

View file

@ -0,0 +1,103 @@
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import type { ILoadOptionsFunctions } from 'n8n-workflow';
import { fetchPartitionKeyField } from '../GenericFunctions';
describe('GenericFunctions - fetchPartitionKeyField', () => {
const mockMicrosoftCosmosDbRequest = jest.fn();
const mockContext = {
helpers: {
requestWithAuthentication: mockMicrosoftCosmosDbRequest,
},
getNodeParameter: jest.fn(),
getCredentials: jest.fn(),
} as unknown as ILoadOptionsFunctions;
beforeEach(() => {
jest.clearAllMocks();
mockContext.getNode = jest.fn().mockReturnValue({});
(mockContext.getCredentials as jest.Mock).mockResolvedValueOnce({
account: 'us-east-1',
database: 'test_database',
baseUrl: 'https://us-east-1.documents.azure.com',
});
});
it('should fetch the partition key successfully', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
mockMicrosoftCosmosDbRequest.mockResolvedValueOnce({
partitionKey: {
paths: ['/PartitionKey'],
kind: 'Hash',
version: 2,
},
});
const response = await fetchPartitionKeyField.call(mockContext);
expect(mockMicrosoftCosmosDbRequest).toHaveBeenCalledWith(
'microsoftCosmosDbSharedKeyApi',
expect.objectContaining({
method: 'GET',
url: '/colls/coll-1',
}),
);
expect(response).toEqual({
results: [
{
name: 'PartitionKey',
value: 'PartitionKey',
},
],
});
});
it('should throw an error when container ID is missing', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({ mode: 'list', value: '' });
await expect(fetchPartitionKeyField.call(mockContext)).rejects.toThrowError(
expect.objectContaining({
message: 'Container is required to determine the partition key.',
}),
);
});
it('should return an empty array if no partition key is found', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
mockMicrosoftCosmosDbRequest.mockResolvedValueOnce({
partitionKey: {
paths: [],
kind: 'Hash',
version: 2,
},
});
const response = await fetchPartitionKeyField.call(mockContext);
expect(response).toEqual({ results: [] });
});
it('should handle unexpected response format gracefully', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
mockMicrosoftCosmosDbRequest.mockResolvedValueOnce({ unexpectedKey: 'value' });
const response = await fetchPartitionKeyField.call(mockContext);
expect(response).toEqual({ results: [] });
});
});

View file

@ -1,3 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { microsoftCosmosDbRequest } from '../GenericFunctions'; import { microsoftCosmosDbRequest } from '../GenericFunctions';
describe('GenericFunctions - microsoftCosmosDbRequest', () => { describe('GenericFunctions - microsoftCosmosDbRequest', () => {

View file

@ -0,0 +1,119 @@
import type { ILoadOptionsFunctions } from 'n8n-workflow';
import { searchItemById } from '../GenericFunctions';
describe('GenericFunctions - searchItemById', () => {
const mockRequestWithAuthentication = jest.fn();
const mockContext = {
helpers: {
requestWithAuthentication: mockRequestWithAuthentication,
},
getNodeParameter: jest.fn(),
getCredentials: jest.fn(),
} as unknown as ILoadOptionsFunctions;
beforeEach(() => {
jest.clearAllMocks();
mockContext.getNode = jest.fn().mockReturnValue({});
});
it('should fetch the item successfully', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
const itemId = 'item-123';
(mockContext.getCredentials as jest.Mock).mockResolvedValueOnce({
account: 'us-east-1',
database: 'first_database_1',
baseUrl: 'https://us-east-1.documents.azure.com',
});
mockRequestWithAuthentication.mockResolvedValueOnce({
id: itemId,
name: 'Test Item',
});
const response = await searchItemById.call(mockContext, itemId);
expect(mockRequestWithAuthentication).toHaveBeenCalledWith(
'microsoftCosmosDbSharedKeyApi',
expect.objectContaining({
method: 'GET',
url: '/colls/coll-1/docs/item-123',
}),
);
expect(response).toEqual({
id: itemId,
name: 'Test Item',
});
});
it('should throw an error when container ID is missing', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({ mode: 'list', value: '' });
await expect(searchItemById.call(mockContext, 'item-123')).rejects.toThrowError(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
expect.objectContaining({
message: 'Container is required',
}),
);
});
it('should throw an error when item ID is missing', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
await expect(searchItemById.call(mockContext, '')).rejects.toThrowError(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
expect.objectContaining({
message: 'Item is required',
}),
);
});
it('should return null if the response is empty', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
const itemId = 'item-123';
(mockContext.getCredentials as jest.Mock).mockResolvedValueOnce({
account: 'us-east-1',
database: 'first_database_1',
baseUrl: 'https://us-east-1.documents.azure.com',
});
mockRequestWithAuthentication.mockResolvedValueOnce(null);
const response = await searchItemById.call(mockContext, itemId);
expect(response).toBeNull();
});
it('should handle unexpected response format gracefully', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({
mode: 'list',
value: 'coll-1',
});
const itemId = 'item-123';
(mockContext.getCredentials as jest.Mock).mockResolvedValueOnce({
account: 'us-east-1',
database: 'first_database_1',
baseUrl: 'https://us-east-1.documents.azure.com',
});
mockRequestWithAuthentication.mockResolvedValueOnce({ unexpectedKey: 'value' });
const response = await searchItemById.call(mockContext, itemId);
expect(response).toEqual({ unexpectedKey: 'value' });
});
});

View file

@ -15,6 +15,7 @@ describe('GenericFunctions - searchItems', () => {
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
mockContext.getNode = jest.fn().mockReturnValue({});
}); });
it('should fetch documents and return formatted results', async () => { it('should fetch documents and return formatted results', async () => {
@ -45,7 +46,7 @@ describe('GenericFunctions - searchItems', () => {
expect(response).toEqual({ expect(response).toEqual({
results: [ results: [
{ name: 'Item1', value: 'Item 1' }, // Space removed from 'Item 1' { name: 'Item1', value: 'Item 1' },
{ name: 'Item2', value: 'Item 2' }, { name: 'Item2', value: 'Item 2' },
], ],
}); });
@ -119,6 +120,11 @@ describe('GenericFunctions - searchItems', () => {
it('should throw an error when container ID is missing', async () => { it('should throw an error when container ID is missing', async () => {
(mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({ mode: 'list', value: '' }); (mockContext.getNodeParameter as jest.Mock).mockReturnValueOnce({ mode: 'list', value: '' });
await expect(searchItems.call(mockContext)).rejects.toThrow('Container is required'); await expect(searchItems.call(mockContext)).rejects.toThrowError(
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
expect.objectContaining({
message: 'Container is required',
}),
);
}); });
}); });