Skip to content

Commit 529320c

Browse files
Adding ServiceBus Trigger Changes (#353)
* Adding ServiceBus Trigger Chnages * Adding ServiceBus Trigger Chnages * Fixing the unit tests and version update * Staging the new veriosn
1 parent 4845f8c commit 529320c

File tree

7 files changed

+244
-13
lines changed

7 files changed

+244
-13
lines changed

package-lock.json

Lines changed: 43 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@azure/functions",
3-
"version": "4.7.1-preview",
3+
"version": "4.7.2-preview",
44
"description": "Microsoft Azure Functions NodeJS Framework",
55
"keywords": [
66
"azure",
@@ -28,7 +28,7 @@
2828
"README.md"
2929
],
3030
"engines": {
31-
"node": ">=18.0"
31+
"node": ">=20.0"
3232
},
3333
"scripts": {
3434
"build": "webpack --mode development",
@@ -41,7 +41,7 @@
4141
"watch": "webpack --watch --mode development"
4242
},
4343
"dependencies": {
44-
"@azure/functions-extensions-base": "0.1.0-preview",
44+
"@azure/functions-extensions-base": "0.2.0-preview",
4545
"cookie": "^0.7.0",
4646
"long": "^4.0.0",
4747
"undici": "^5.13.0"

src/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
export const version = '4.7.1-preview';
4+
export const version = '4.7.2-preview';
55

66
export const returnBindingKey = '$return';

src/converters/fromRpcTypedData.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ export function fromRpcTypedData(data: RpcTypedData | null | undefined): unknown
3333
return data.collectionSint64.sint64;
3434
} else if (data.modelBindingData && isDefined(data.modelBindingData.content)) {
3535
try {
36-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
3736
const resourceFactoryResolver: ResourceFactoryResolver = ResourceFactoryResolver.getInstance();
3837
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
3938
return resourceFactoryResolver.createClient(data.modelBindingData.source, data.modelBindingData);
@@ -43,6 +42,24 @@ export function fromRpcTypedData(data: RpcTypedData | null | undefined): unknown
4342
`Error: ${exception instanceof Error ? exception.message : String(exception)}`
4443
);
4544
}
45+
} else if (
46+
data.collectionModelBindingData &&
47+
isDefined(data.collectionModelBindingData.modelBindingData) &&
48+
data.collectionModelBindingData.modelBindingData.length > 0
49+
) {
50+
try {
51+
const resourceFactoryResolver: ResourceFactoryResolver = ResourceFactoryResolver.getInstance();
52+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
53+
return resourceFactoryResolver.createClient(
54+
data.collectionModelBindingData.modelBindingData[0]?.source,
55+
data.collectionModelBindingData.modelBindingData
56+
);
57+
} catch (exception) {
58+
throw new Error(
59+
'Unable to create client. Please register the extensions library with your function app. ' +
60+
`Error: ${exception instanceof Error ? exception.message : String(exception)}`
61+
);
62+
}
4663
}
4764
}
4865

test/converters/fromRpcTypedData.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,164 @@ describe('fromRpcTypedData - modelBindingData path', () => {
270270
);
271271
});
272272
});
273+
describe('fromRpcTypedData - collectionModelBindingData path', () => {
274+
let sandbox: sinon.SinonSandbox;
275+
let originalGetInstance: typeof ResourceFactoryResolver.getInstance;
276+
277+
beforeEach(() => {
278+
sandbox = sinon.createSandbox();
279+
originalGetInstance = ResourceFactoryResolver.getInstance.bind(ResourceFactoryResolver);
280+
});
281+
282+
afterEach(() => {
283+
sandbox.restore();
284+
ResourceFactoryResolver.getInstance = originalGetInstance;
285+
});
286+
287+
it('should successfully create a client when collectionModelBindingData is valid', () => {
288+
const mockClient = { name: 'testCollectionClient' };
289+
const mockResolver = {
290+
createClient: sinon.stub().returns(mockClient),
291+
};
292+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
293+
294+
const collectionModelBindingData = {
295+
modelBindingData: [
296+
{
297+
content: Buffer.from('test-content-1'),
298+
source: 'blob',
299+
contentType: 'application/octet-stream',
300+
},
301+
{
302+
content: Buffer.from('test-content-2'),
303+
source: 'blob',
304+
contentType: 'application/octet-stream',
305+
},
306+
],
307+
};
308+
309+
const data: RpcTypedData = {
310+
collectionModelBindingData,
311+
};
312+
313+
const result = fromRpcTypedData(data);
314+
315+
sinon.assert.calledWith(mockResolver.createClient, 'blob', collectionModelBindingData.modelBindingData);
316+
expect(result).to.equal(mockClient);
317+
});
318+
319+
it('should handle collectionModelBindingData with undefined source', () => {
320+
const mockClient = { name: 'testCollectionClient' };
321+
const mockResolver = {
322+
createClient: sinon.stub().returns(mockClient),
323+
};
324+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
325+
326+
const collectionModelBindingData = {
327+
modelBindingData: [
328+
{
329+
content: Buffer.from('test-content-1'),
330+
// source is undefined
331+
contentType: 'application/octet-stream',
332+
},
333+
],
334+
};
335+
336+
const data: RpcTypedData = {
337+
collectionModelBindingData,
338+
};
339+
340+
const result = fromRpcTypedData(data);
341+
342+
expect(mockResolver.createClient.calledWith(undefined, collectionModelBindingData.modelBindingData)).to.be.true;
343+
expect(result).to.equal(mockClient);
344+
});
345+
346+
it('should throw enhanced error when ResourceFactoryResolver.createClient throws for collectionModelBindingData', () => {
347+
const originalError = new Error('Collection factory not registered');
348+
const mockResolver = {
349+
createClient: sinon.stub().throws(originalError),
350+
};
351+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
352+
353+
const collectionModelBindingData = {
354+
modelBindingData: [
355+
{
356+
content: Buffer.from('test-content-1'),
357+
source: 'blob',
358+
contentType: 'application/octet-stream',
359+
},
360+
],
361+
};
362+
363+
const data: RpcTypedData = {
364+
collectionModelBindingData,
365+
};
366+
367+
expect(() => fromRpcTypedData(data)).to.throw(
368+
'Unable to create client. Please register the extensions library with your function app. ' +
369+
'Error: Collection factory not registered'
370+
);
371+
});
372+
373+
it('should throw enhanced error when ResourceFactoryResolver.getInstance throws for collectionModelBindingData', () => {
374+
const originalError = new Error('Collection resolver not initialized');
375+
ResourceFactoryResolver.getInstance = sinon.stub().throws(originalError);
376+
377+
const collectionModelBindingData = {
378+
modelBindingData: [
379+
{
380+
content: Buffer.from('test-content-1'),
381+
source: 'blob',
382+
contentType: 'application/octet-stream',
383+
},
384+
],
385+
};
386+
387+
const data: RpcTypedData = {
388+
collectionModelBindingData,
389+
};
390+
391+
expect(() => fromRpcTypedData(data)).to.throw(
392+
'Unable to create client. Please register the extensions library with your function app. ' +
393+
'Error: Collection resolver not initialized'
394+
);
395+
});
396+
397+
it('should handle non-Error exceptions by converting to string for collectionModelBindingData', () => {
398+
const mockResolver = {
399+
createClient: sinon.stub().throws('String exception for collection'), // Non-Error exception
400+
};
401+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
402+
403+
const collectionModelBindingData = {
404+
modelBindingData: [
405+
{
406+
content: Buffer.from('test-content-1'),
407+
source: 'blob',
408+
contentType: 'application/octet-stream',
409+
},
410+
],
411+
};
412+
413+
const data: RpcTypedData = {
414+
collectionModelBindingData,
415+
};
416+
417+
expect(() => fromRpcTypedData(data)).to.throw(
418+
'Unable to create client. Please register the extensions library with your function app. ' +
419+
'Error: Sinon-provided String exception for collection'
420+
);
421+
});
422+
});
423+
424+
describe('fromRpcTypedData - fallback/undefined cases', () => {
425+
it('should return undefined for unknown data shape', () => {
426+
const data: RpcTypedData = { foo: 'bar' } as any;
427+
expect(fromRpcTypedData(data)).to.be.undefined;
428+
});
429+
430+
it('should return undefined for empty object', () => {
431+
expect(fromRpcTypedData({} as RpcTypedData)).to.be.undefined;
432+
});
433+
});

types-core/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,12 @@ declare module '@azure/functions-core' {
441441
collectionSint64?: RpcCollectionSInt64 | null;
442442

443443
modelBindingData?: ModelBindingData | null;
444+
445+
collectionModelBindingData?: CollectionModelBindingData | null;
446+
}
447+
448+
export interface CollectionModelBindingData {
449+
modelBindingData?: ModelBindingData[] | null;
444450
}
445451

446452
export interface ModelBindingData {

types/serviceBus.d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,22 @@ export interface ServiceBusQueueTriggerOptions {
2828
*/
2929
isSessionsEnabled?: boolean;
3030

31+
/**
32+
* Gets or sets a value indicating whether the trigger should automatically complete the message after successful processing.
33+
* If not explicitly set, the behavior will be based on the autoCompleteMessages configuration in host.json.
34+
* For more information, <see cref="https://aka.ms/AAp8dm9"/>"
35+
*/
36+
autoCompleteMessages?: boolean;
37+
3138
/**
3239
* Set to `many` in order to enable batching. If omitted or set to `one`, a single message is passed to the function.
3340
*/
3441
cardinality?: 'many' | 'one';
42+
43+
/**
44+
* Whether to use sdk binding for this blob operation.
45+
* */
46+
sdkBinding?: boolean;
3547
}
3648
export type ServiceBusQueueTrigger = FunctionTrigger & ServiceBusQueueTriggerOptions;
3749

0 commit comments

Comments
 (0)