Skip to content

Commit 1e1c720

Browse files
committed
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 14ce62c commit 1e1c720

File tree

6 files changed

+376
-4
lines changed

6 files changed

+376
-4
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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",

src/converters/fromRpcTypedData.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,35 @@ export function fromRpcTypedData(data: RpcTypedData | null | undefined): unknown
3030
return data.collectionDouble.double;
3131
} else if (data.collectionSint64 && isDefined(data.collectionSint64.sint64)) {
3232
return data.collectionSint64.sint64;
33-
} else {
34-
return undefined;
33+
} else if (data.modelBindingData && isDefined(data.modelBindingData.content)) {
34+
try {
35+
const resourceFactoryResolver: ResourceFactoryResolver = ResourceFactoryResolver.getInstance();
36+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
37+
return resourceFactoryResolver.createClient(data.modelBindingData.source, data.modelBindingData);
38+
} catch (exception) {
39+
throw new Error(
40+
'Unable to create client. Please register the extensions library with your function app. ' +
41+
`Error: ${exception instanceof Error ? exception.message : String(exception)}`
42+
);
43+
}
44+
} else if (
45+
data.collectionModelBindingData &&
46+
isDefined(data.collectionModelBindingData.modelBindingData) &&
47+
data.collectionModelBindingData.modelBindingData.length > 0
48+
) {
49+
try {
50+
const resourceFactoryResolver: ResourceFactoryResolver = ResourceFactoryResolver.getInstance();
51+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
52+
return resourceFactoryResolver.createClient(
53+
data.collectionModelBindingData.modelBindingData[0]?.source,
54+
data.collectionModelBindingData.modelBindingData
55+
);
56+
} catch (exception) {
57+
throw new Error(
58+
'Unable to create client. Please register the extensions library with your function app. ' +
59+
`Error: ${exception instanceof Error ? exception.message : String(exception)}`
60+
);
61+
}
3562
}
3663
}
3764

test/converters/fromRpcTypedData.test.ts

Lines changed: 318 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,321 @@ describe('fromRpcTypedData', () => {
110110
expect(result[1].toString()).to.equal('9007199254740992');
111111
});
112112
});
113+
114+
describe('fromRpcTypedData - modelBindingData path', () => {
115+
// Use SinonSandbox for automatic cleanup of stubs
116+
let sandbox: sinon.SinonSandbox;
117+
118+
// Store original ResourceFactoryResolver.getInstance to restore after tests
119+
let originalGetInstance: typeof ResourceFactoryResolver.getInstance;
120+
121+
beforeEach(() => {
122+
sandbox = sinon.createSandbox();
123+
// Store original method
124+
originalGetInstance = ResourceFactoryResolver.getInstance.bind(ResourceFactoryResolver);
125+
});
126+
127+
afterEach(() => {
128+
// Restore all stubs and original methods
129+
sandbox.restore();
130+
ResourceFactoryResolver.getInstance = originalGetInstance;
131+
});
132+
133+
it('should successfully create a client when modelBindingData is valid', () => {
134+
// Arrange
135+
const mockClient = {
136+
name: 'testClient',
137+
download: () => Promise.resolve({ readableStreamBody: Buffer.from('test') }),
138+
};
139+
140+
// Create mock ResourceFactoryResolver
141+
const mockResolver = {
142+
createClient: sinon.stub().returns(mockClient),
143+
};
144+
145+
// Replace ResourceFactoryResolver.getInstance with our mock
146+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
147+
148+
// Create test data
149+
const modelBindingData = {
150+
content: Buffer.from('test-content'),
151+
source: 'blob',
152+
contentType: 'application/octet-stream',
153+
};
154+
155+
const data: RpcTypedData = {
156+
modelBindingData: modelBindingData,
157+
};
158+
159+
// Act
160+
const result = fromRpcTypedData(data);
161+
162+
// Assert
163+
sinon.assert.calledWith(mockResolver.createClient, 'blob', modelBindingData);
164+
expect(result).to.equal(mockClient);
165+
});
166+
167+
it('should handle modelBindingData with undefined source', () => {
168+
// Arrange
169+
const mockClient = { name: 'testClient' };
170+
171+
const mockResolver = {
172+
createClient: sinon.stub().returns(mockClient),
173+
};
174+
175+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
176+
177+
const modelBindingData = {
178+
content: Buffer.from('test-content'),
179+
// No source specified
180+
contentType: 'application/octet-stream',
181+
};
182+
183+
const data: RpcTypedData = {
184+
modelBindingData: modelBindingData,
185+
};
186+
187+
// Act
188+
const result = fromRpcTypedData(data);
189+
190+
// Assert
191+
expect(mockResolver.createClient.calledWith(undefined, modelBindingData)).to.be.true;
192+
expect(result).to.equal(mockClient);
193+
});
194+
195+
it('should throw enhanced error when ResourceFactoryResolver.createClient throws', () => {
196+
// Arrange
197+
const originalError = new Error('Factory not registered');
198+
199+
const mockResolver = {
200+
createClient: sinon.stub().throws(originalError),
201+
};
202+
203+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
204+
205+
const modelBindingData = {
206+
content: Buffer.from('test-content'),
207+
source: 'blob',
208+
contentType: 'application/octet-stream',
209+
};
210+
211+
const data: RpcTypedData = {
212+
modelBindingData: modelBindingData,
213+
};
214+
215+
// Act & Assert
216+
expect(() => fromRpcTypedData(data)).to.throw(
217+
'Unable to create client. Please register the extensions library with your function app. ' +
218+
'Error: Factory not registered'
219+
);
220+
});
221+
222+
it('should throw enhanced error when ResourceFactoryResolver.getInstance throws', () => {
223+
// Arrange
224+
const originalError = new Error('Resolver not initialized');
225+
226+
ResourceFactoryResolver.getInstance = sinon.stub().throws(originalError);
227+
228+
const modelBindingData = {
229+
content: Buffer.from('test-content'),
230+
source: 'blob',
231+
contentType: 'application/octet-stream',
232+
};
233+
234+
const data: RpcTypedData = {
235+
modelBindingData: modelBindingData,
236+
};
237+
238+
// Act & Assert
239+
expect(() => fromRpcTypedData(data)).to.throw(
240+
'Unable to create client. Please register the extensions library with your function app. ' +
241+
'Error: Resolver not initialized'
242+
);
243+
});
244+
245+
it('should handle non-Error exceptions by converting to string', () => {
246+
// Arrange
247+
const mockResolver = {
248+
createClient: sinon.stub().throws('String exception'), // Non-Error exception
249+
};
250+
251+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
252+
253+
const modelBindingData = {
254+
content: Buffer.from('test-content'),
255+
source: 'blob',
256+
contentType: 'application/octet-stream',
257+
};
258+
259+
const data: RpcTypedData = {
260+
modelBindingData: modelBindingData,
261+
};
262+
263+
// Act & Assert
264+
expect(() => fromRpcTypedData(data)).to.throw(
265+
'Unable to create client. Please register the extensions library with your function app. ' +
266+
'Error: Sinon-provided String exception'
267+
);
268+
});
269+
});
270+
describe('fromRpcTypedData - collectionModelBindingData path', () => {
271+
let sandbox: sinon.SinonSandbox;
272+
let originalGetInstance: typeof ResourceFactoryResolver.getInstance;
273+
274+
beforeEach(() => {
275+
sandbox = sinon.createSandbox();
276+
originalGetInstance = ResourceFactoryResolver.getInstance.bind(ResourceFactoryResolver);
277+
});
278+
279+
afterEach(() => {
280+
sandbox.restore();
281+
ResourceFactoryResolver.getInstance = originalGetInstance;
282+
});
283+
284+
it('should successfully create a client when collectionModelBindingData is valid', () => {
285+
const mockClient = { name: 'testCollectionClient' };
286+
const mockResolver = {
287+
createClient: sinon.stub().returns(mockClient),
288+
};
289+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
290+
291+
const collectionModelBindingData = {
292+
modelBindingData: [
293+
{
294+
content: Buffer.from('test-content-1'),
295+
source: 'blob',
296+
contentType: 'application/octet-stream',
297+
},
298+
{
299+
content: Buffer.from('test-content-2'),
300+
source: 'blob',
301+
contentType: 'application/octet-stream',
302+
},
303+
],
304+
};
305+
306+
const data: RpcTypedData = {
307+
collectionModelBindingData,
308+
};
309+
310+
const result = fromRpcTypedData(data);
311+
312+
sinon.assert.calledWith(mockResolver.createClient, 'blob', collectionModelBindingData.modelBindingData);
313+
expect(result).to.equal(mockClient);
314+
});
315+
316+
it('should handle collectionModelBindingData with undefined source', () => {
317+
const mockClient = { name: 'testCollectionClient' };
318+
const mockResolver = {
319+
createClient: sinon.stub().returns(mockClient),
320+
};
321+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
322+
323+
const collectionModelBindingData = {
324+
modelBindingData: [
325+
{
326+
content: Buffer.from('test-content-1'),
327+
// source is undefined
328+
contentType: 'application/octet-stream',
329+
},
330+
],
331+
};
332+
333+
const data: RpcTypedData = {
334+
collectionModelBindingData,
335+
};
336+
337+
const result = fromRpcTypedData(data);
338+
339+
expect(mockResolver.createClient.calledWith(undefined, collectionModelBindingData.modelBindingData)).to.be.true;
340+
expect(result).to.equal(mockClient);
341+
});
342+
343+
it('should throw enhanced error when ResourceFactoryResolver.createClient throws for collectionModelBindingData', () => {
344+
const originalError = new Error('Collection factory not registered');
345+
const mockResolver = {
346+
createClient: sinon.stub().throws(originalError),
347+
};
348+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
349+
350+
const collectionModelBindingData = {
351+
modelBindingData: [
352+
{
353+
content: Buffer.from('test-content-1'),
354+
source: 'blob',
355+
contentType: 'application/octet-stream',
356+
},
357+
],
358+
};
359+
360+
const data: RpcTypedData = {
361+
collectionModelBindingData,
362+
};
363+
364+
expect(() => fromRpcTypedData(data)).to.throw(
365+
'Unable to create client. Please register the extensions library with your function app. ' +
366+
'Error: Collection factory not registered'
367+
);
368+
});
369+
370+
it('should throw enhanced error when ResourceFactoryResolver.getInstance throws for collectionModelBindingData', () => {
371+
const originalError = new Error('Collection resolver not initialized');
372+
ResourceFactoryResolver.getInstance = sinon.stub().throws(originalError);
373+
374+
const collectionModelBindingData = {
375+
modelBindingData: [
376+
{
377+
content: Buffer.from('test-content-1'),
378+
source: 'blob',
379+
contentType: 'application/octet-stream',
380+
},
381+
],
382+
};
383+
384+
const data: RpcTypedData = {
385+
collectionModelBindingData,
386+
};
387+
388+
expect(() => fromRpcTypedData(data)).to.throw(
389+
'Unable to create client. Please register the extensions library with your function app. ' +
390+
'Error: Collection resolver not initialized'
391+
);
392+
});
393+
394+
it('should handle non-Error exceptions by converting to string for collectionModelBindingData', () => {
395+
const mockResolver = {
396+
createClient: sinon.stub().throws('String exception for collection'), // Non-Error exception
397+
};
398+
ResourceFactoryResolver.getInstance = sinon.stub().returns(mockResolver);
399+
400+
const collectionModelBindingData = {
401+
modelBindingData: [
402+
{
403+
content: Buffer.from('test-content-1'),
404+
source: 'blob',
405+
contentType: 'application/octet-stream',
406+
},
407+
],
408+
};
409+
410+
const data: RpcTypedData = {
411+
collectionModelBindingData,
412+
};
413+
414+
expect(() => fromRpcTypedData(data)).to.throw(
415+
'Unable to create client. Please register the extensions library with your function app. ' +
416+
'Error: Sinon-provided String exception for collection'
417+
);
418+
});
419+
});
420+
421+
describe('fromRpcTypedData - fallback/undefined cases', () => {
422+
it('should return undefined for unknown data shape', () => {
423+
const data: RpcTypedData = { foo: 'bar' } as any;
424+
expect(fromRpcTypedData(data)).to.be.undefined;
425+
});
426+
427+
it('should return undefined for empty object', () => {
428+
expect(fromRpcTypedData({} as RpcTypedData)).to.be.undefined;
429+
});
430+
});

types-core/index.d.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,21 @@ declare module '@azure/functions-core' {
433433
collectionDouble?: RpcCollectionDouble | null;
434434

435435
collectionSint64?: RpcCollectionSInt64 | null;
436+
437+
modelBindingData?: ModelBindingData | null;
438+
439+
collectionModelBindingData?: CollectionModelBindingData | null;
440+
}
441+
442+
export interface CollectionModelBindingData {
443+
modelBindingData?: ModelBindingData[] | null;
444+
}
445+
446+
export interface ModelBindingData {
447+
content?: Buffer | null;
448+
contentType?: string | null;
449+
source?: string | null;
450+
version?: string | null;
436451
}
437452

438453
interface RpcCollectionSInt64 {

0 commit comments

Comments
 (0)