mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
Fixed requests
This commit is contained in:
parent
9801c907fc
commit
d210fcc172
|
@ -75,15 +75,22 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
|
|||
requestOptions.headers['x-ms-session-token'] = credentials.sessionToken;
|
||||
}
|
||||
|
||||
// const url = new URL (requestOptions.uri);
|
||||
let url;
|
||||
|
||||
const url = new URL(requestOptions.baseURL + requestOptions.url);
|
||||
const pathSegments = url.pathname.split('/').filter((segment) => segment);
|
||||
if (requestOptions.url) {
|
||||
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 resourceId = '';
|
||||
|
||||
if (pathSegments.includes('docs')) {
|
||||
if (pathSegments?.includes('docs')) {
|
||||
const docsIndex = pathSegments.lastIndexOf('docs');
|
||||
resourceType = 'docs';
|
||||
if (pathSegments[docsIndex + 1]) {
|
||||
|
@ -92,7 +99,7 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
|
|||
} else {
|
||||
resourceId = pathSegments.slice(0, docsIndex).join('/');
|
||||
}
|
||||
} else if (pathSegments.includes('colls')) {
|
||||
} else if (pathSegments?.includes('colls')) {
|
||||
const collsIndex = pathSegments.lastIndexOf('colls');
|
||||
resourceType = 'colls';
|
||||
if (pathSegments[collsIndex + 1]) {
|
||||
|
@ -101,7 +108,7 @@ export class MicrosoftCosmosDbSharedKeyApi implements ICredentialType {
|
|||
} else {
|
||||
resourceId = pathSegments.slice(0, collsIndex).join('/');
|
||||
}
|
||||
} else if (pathSegments.includes('dbs')) {
|
||||
} else if (pathSegments?.includes('dbs')) {
|
||||
const dbsIndex = pathSegments.lastIndexOf('dbs');
|
||||
resourceType = 'dbs';
|
||||
resourceId = pathSegments.slice(0, dbsIndex + 2).join('/');
|
||||
|
|
|
@ -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 type {
|
||||
DeclarativeRestApiSettings,
|
||||
|
@ -71,6 +75,7 @@ export async function microsoftCosmosDbRequest(
|
|||
|
||||
const requestOptions: IHttpRequestOptions = {
|
||||
...opts,
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
|
||||
baseURL: `${credentials.baseUrl}`,
|
||||
headers: {
|
||||
...opts.headers,
|
||||
|
@ -99,8 +104,6 @@ export async function microsoftCosmosDbRequest(
|
|||
};
|
||||
|
||||
try {
|
||||
console.log('Final Request Options before Request:', requestOptions);
|
||||
|
||||
return (await this.helpers.requestWithAuthentication.call(
|
||||
this,
|
||||
'microsoftCosmosDbSharedKeyApi',
|
||||
|
@ -442,14 +445,15 @@ export async function validateOperations(
|
|||
);
|
||||
}
|
||||
|
||||
//To-Do-check to not send properties it doesn't need
|
||||
return {
|
||||
op: operation.op,
|
||||
path: operation.op === 'move' ? operation.toPath?.value : operation.path?.value,
|
||||
...(operation.from ? { from: operation.from.value } : {}),
|
||||
...(operation.op === 'incr'
|
||||
? { 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;
|
||||
}
|
||||
|
||||
export async function validateFields(
|
||||
export async function validateContainerFields(
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
): Promise<IHttpRequestOptions> {
|
||||
const additionalFields = this.getNodeParameter('additionalFields', {}) as IDataObject;
|
||||
const indexingPolicy = additionalFields.indexingPolicy;
|
||||
const manualThroughput = additionalFields.offerThroughput;
|
||||
const autoscaleThroughput = additionalFields.maxThroughput;
|
||||
|
||||
|
@ -473,32 +476,28 @@ export async function validateFields(
|
|||
{},
|
||||
{
|
||||
message: 'Bad parameter',
|
||||
description:
|
||||
'Please choose only one of Max RU/s (Autoscale) and Max RU/s (Manual Throughput)',
|
||||
description: 'Please choose only one of Max RU/s (Autoscale) and Manual Throughput RU/s',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (autoscaleThroughput && requestOptions?.qs) {
|
||||
requestOptions.qs['x-ms-cosmos-offer-autopilot-settings'] = {
|
||||
maxThroughput: autoscaleThroughput,
|
||||
|
||||
if (autoscaleThroughput) {
|
||||
requestOptions.headers = {
|
||||
...requestOptions.headers,
|
||||
'x-ms-cosmos-offer-autopilot-setting': { maxThroughput: autoscaleThroughput },
|
||||
};
|
||||
}
|
||||
|
||||
if (!indexingPolicy || Object.keys(indexingPolicy).length === 0) {
|
||||
throw new NodeApiError(
|
||||
this.getNode(),
|
||||
{},
|
||||
{
|
||||
message: 'Invalid Indexing Policy',
|
||||
description: 'Please provide a valid indexingPolicy JSON.',
|
||||
},
|
||||
);
|
||||
if (manualThroughput) {
|
||||
requestOptions.headers = {
|
||||
...requestOptions.headers,
|
||||
'x-ms-offer-throughput': manualThroughput,
|
||||
};
|
||||
}
|
||||
|
||||
return requestOptions;
|
||||
}
|
||||
|
||||
//WIP
|
||||
export async function handlePagination(
|
||||
this: IExecutePaginationFunctions,
|
||||
resultOptions: DeclarativeRestApiSettings.ResultOptions,
|
||||
|
@ -533,7 +532,6 @@ export async function handlePagination(
|
|||
}
|
||||
}
|
||||
|
||||
//TO-DO-check-if-works
|
||||
if (responseData.length > 0) {
|
||||
const lastItem = responseData[responseData.length - 1];
|
||||
|
||||
|
@ -554,40 +552,34 @@ export async function handlePagination(
|
|||
return aggregatedResult.map((result) => ({ json: result }));
|
||||
}
|
||||
|
||||
//WIP
|
||||
export async function handleErrorPostReceive(
|
||||
this: IExecuteSingleFunctions,
|
||||
data: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
console.log('Status code❌', response.statusCode);
|
||||
|
||||
if (String(response.statusCode).startsWith('4') || String(response.statusCode).startsWith('5')) {
|
||||
const responseBody = response.body as IDataObject;
|
||||
console.log('Got here ❌', responseBody);
|
||||
|
||||
let errorMessage = 'Unknown error occurred';
|
||||
let errorDescription = 'An unexpected error was encountered.';
|
||||
|
||||
if (typeof responseBody.message === 'string') {
|
||||
try {
|
||||
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';
|
||||
if (typeof responseBody === 'object' && responseBody !== null) {
|
||||
if (typeof responseBody.code === 'string') {
|
||||
errorMessage = responseBody.code;
|
||||
}
|
||||
if (typeof responseBody.message === 'string') {
|
||||
errorDescription = responseBody.message;
|
||||
}
|
||||
}
|
||||
|
||||
throw new ApplicationError(errorMessage);
|
||||
throw new NodeApiError(
|
||||
this.getNode(),
|
||||
{},
|
||||
{
|
||||
message: errorMessage,
|
||||
description: errorDescription,
|
||||
},
|
||||
);
|
||||
}
|
||||
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[] = [];
|
||||
|
||||
Object.entries(obj).forEach(([key, value]) => {
|
||||
|
@ -692,7 +684,7 @@ function extractFieldPaths(obj: any, prefix = ''): string[] {
|
|||
}
|
||||
});
|
||||
} else if (typeof value === 'object' && value !== null) {
|
||||
paths = paths.concat(extractFieldPaths(value, newPath));
|
||||
paths = paths.concat(extractFieldPaths(value as IDataObject, newPath));
|
||||
} else {
|
||||
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(
|
||||
this: IExecuteSingleFunctions,
|
||||
requestOptions: IHttpRequestOptions,
|
||||
|
@ -803,7 +823,6 @@ export async function formatCustomProperties(
|
|||
|
||||
let parsedProperties: Record<string, unknown>;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parsedProperties = JSON.parse(rawCustomProperties);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(
|
||||
|
@ -841,11 +860,9 @@ export async function formatJSONFields(
|
|||
let parsedIndexPolicy: Record<string, unknown> | undefined;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parsedPartitionKey = JSON.parse(rawPartitionKey);
|
||||
|
||||
if (indexingPolicy) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
parsedIndexPolicy = JSON.parse(indexingPolicy);
|
||||
}
|
||||
} catch (error) {
|
||||
|
@ -880,32 +897,32 @@ export async function processResponseItems(
|
|||
this: IExecuteSingleFunctions,
|
||||
items: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<any> {
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (!response || typeof response !== 'object' || !Array.isArray(items)) {
|
||||
throw new ApplicationError('Invalid response format from Cosmos DB.');
|
||||
}
|
||||
|
||||
const extractedDocuments: IDataObject[] = items.flatMap((item) => {
|
||||
const extractedDocuments: INodeExecutionData[] = items.flatMap((item) => {
|
||||
if (
|
||||
item.json &&
|
||||
typeof item.json === 'object' &&
|
||||
'Documents' in item.json &&
|
||||
Array.isArray(item.json.Documents)
|
||||
) {
|
||||
return item.json.Documents as IDataObject[];
|
||||
return item.json.Documents.map((doc) => ({ json: doc }));
|
||||
}
|
||||
|
||||
return [];
|
||||
});
|
||||
|
||||
return extractedDocuments;
|
||||
return extractedDocuments.length ? extractedDocuments : [{ json: {} }];
|
||||
}
|
||||
|
||||
export async function processResponseContainers(
|
||||
this: IExecuteSingleFunctions,
|
||||
items: INodeExecutionData[],
|
||||
response: IN8nHttpFullResponse,
|
||||
): Promise<any> {
|
||||
): Promise<INodeExecutionData[]> {
|
||||
if (!response || typeof response !== 'object' || !Array.isArray(items)) {
|
||||
throw new ApplicationError('Invalid response format from Cosmos DB.');
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
import { formatJSONFields, processResponseContainers, validateFields } from '../GenericFunctions';
|
||||
import {
|
||||
formatJSONFields,
|
||||
handleErrorPostReceive,
|
||||
processResponseContainers,
|
||||
validateContainerFields,
|
||||
} from '../GenericFunctions';
|
||||
|
||||
export const containerOperations: INodeProperties[] = [
|
||||
{
|
||||
|
@ -20,13 +25,16 @@ export const containerOperations: INodeProperties[] = [
|
|||
description: 'Create a container',
|
||||
routing: {
|
||||
send: {
|
||||
preSend: [formatJSONFields, validateFields],
|
||||
preSend: [formatJSONFields, validateContainerFields],
|
||||
},
|
||||
request: {
|
||||
ignoreHttpStatusErrors: true,
|
||||
method: 'POST',
|
||||
url: '/colls',
|
||||
},
|
||||
output: {
|
||||
postReceive: [handleErrorPostReceive],
|
||||
},
|
||||
},
|
||||
action: 'Create container',
|
||||
},
|
||||
|
@ -42,6 +50,7 @@ export const containerOperations: INodeProperties[] = [
|
|||
},
|
||||
output: {
|
||||
postReceive: [
|
||||
handleErrorPostReceive,
|
||||
{
|
||||
type: 'set',
|
||||
properties: {
|
||||
|
@ -63,6 +72,9 @@ export const containerOperations: INodeProperties[] = [
|
|||
method: 'GET',
|
||||
url: '=/colls/{{ $parameter["collId"] }}',
|
||||
},
|
||||
output: {
|
||||
postReceive: [handleErrorPostReceive],
|
||||
},
|
||||
},
|
||||
action: 'Get container',
|
||||
},
|
||||
|
@ -77,7 +89,7 @@ export const containerOperations: INodeProperties[] = [
|
|||
url: '/colls',
|
||||
},
|
||||
output: {
|
||||
postReceive: [processResponseContainers],
|
||||
postReceive: [handleErrorPostReceive, processResponseContainers],
|
||||
},
|
||||
},
|
||||
action: 'Get many containers',
|
||||
|
@ -156,19 +168,15 @@ export const createFields: INodeProperties[] = [
|
|||
description: 'The user specified autoscale max RU/s',
|
||||
},
|
||||
{
|
||||
displayName: 'Max RU/s (for Manual Throughput)',
|
||||
displayName: 'Manual Throughput RU/s',
|
||||
name: 'offerThroughput',
|
||||
type: 'number',
|
||||
default: 400,
|
||||
typeOptions: {
|
||||
minValue: 400,
|
||||
},
|
||||
description:
|
||||
'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',
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
formatCustomProperties,
|
||||
handleErrorPostReceive,
|
||||
handlePagination,
|
||||
presendLimitField,
|
||||
processResponseItems,
|
||||
validateOperations,
|
||||
validatePartitionKey,
|
||||
|
@ -97,6 +98,7 @@ export const itemOperations: INodeProperties[] = [
|
|||
routing: {
|
||||
send: {
|
||||
paginate: true,
|
||||
preSend: [presendLimitField],
|
||||
},
|
||||
operations: {
|
||||
pagination: handlePagination,
|
||||
|
@ -128,7 +130,7 @@ export const itemOperations: INodeProperties[] = [
|
|||
},
|
||||
},
|
||||
output: {
|
||||
postReceive: [handleErrorPostReceive],
|
||||
postReceive: [processResponseItems, handleErrorPostReceive],
|
||||
},
|
||||
},
|
||||
action: 'Query items',
|
||||
|
@ -539,13 +541,6 @@ export const getAllFields: INodeProperties[] = [
|
|||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
routing: {
|
||||
send: {
|
||||
property: 'x-ms-max-item-count',
|
||||
type: 'query',
|
||||
value: '={{ $value }}',
|
||||
},
|
||||
},
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
|
@ -611,7 +606,7 @@ export const queryFields: INodeProperties[] = [
|
|||
operation: ['query'],
|
||||
},
|
||||
},
|
||||
placeholder: 'SELECT * FROM c WHERE c.name = @name',
|
||||
placeholder: 'SELECT * FROM c WHERE c.name = @Name',
|
||||
routing: {
|
||||
send: {
|
||||
type: 'body',
|
||||
|
|
|
@ -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: [] });
|
||||
});
|
||||
});
|
|
@ -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';
|
||||
|
||||
describe('GenericFunctions - microsoftCosmosDbRequest', () => {
|
||||
|
|
|
@ -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' });
|
||||
});
|
||||
});
|
|
@ -15,6 +15,7 @@ describe('GenericFunctions - searchItems', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockContext.getNode = jest.fn().mockReturnValue({});
|
||||
});
|
||||
|
||||
it('should fetch documents and return formatted results', async () => {
|
||||
|
@ -45,7 +46,7 @@ describe('GenericFunctions - searchItems', () => {
|
|||
|
||||
expect(response).toEqual({
|
||||
results: [
|
||||
{ name: 'Item1', value: 'Item 1' }, // Space removed from 'Item 1'
|
||||
{ name: 'Item1', value: 'Item 1' },
|
||||
{ name: 'Item2', value: 'Item 2' },
|
||||
],
|
||||
});
|
||||
|
@ -119,6 +120,11 @@ describe('GenericFunctions - searchItems', () => {
|
|||
it('should throw an error when container ID is missing', async () => {
|
||||
(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',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue