Skip to content

Commit 8004e34

Browse files
authored
Add basic hook tests (#26)
1 parent 99414b8 commit 8004e34

File tree

4 files changed

+129
-2
lines changed

4 files changed

+129
-2
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Here is the general flow of the pipeline:
2323

2424
## Running locally
2525

26+
### Install core tools
27+
28+
1. Run `./scripts/install-func-cli.ps1` in PowerShell to install the latest nightly build of Azure Functions Core Tools
29+
2630
### Build
2731

2832
1. Run `npm run install` and `npm run build` in the root directory, and in the test app folders (`app/v3` and `app/v4`)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { app, HttpRequest, HttpResponseInit, InvocationContext } from '@azure/functions';
5+
6+
export async function httpTriggerForHooks(
7+
request: HttpRequest,
8+
extraInput: string,
9+
context: InvocationContext
10+
): Promise<HttpResponseInit> {
11+
context.log(`httpTriggerForHooks was triggered with second input ${extraInput}`);
12+
return { body: 'hookBodyResponse' };
13+
}
14+
15+
app.http('httpTriggerForHooks', {
16+
methods: ['GET', 'POST'],
17+
authLevel: 'anonymous',
18+
handler: <any>httpTriggerForHooks,
19+
});

app/v4/src/index.ts

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,65 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the MIT License.
33

4-
import { registerHook } from '@azure/functions-core';
4+
import { app } from '@azure/functions';
55

6-
registerHook('postInvocation', async () => {
6+
app.hook.appStart((context) => {
7+
context.hookData.testData = 'appStartHookData123';
8+
console.log('appStart hook executed.');
9+
});
10+
11+
app.hook.appTerminate((context) => {
12+
console.log(`appTerminate hook executed with hook data ${context.hookData.testData}`);
13+
});
14+
15+
app.hook.preInvocation((context) => {
16+
if (context.invocationContext.functionName === 'httpTriggerForHooks') {
17+
context.invocationContext.log(
18+
`preInvocation hook executed with inputs ${JSON.stringify(context.inputs.map((v) => v.constructor.name))}`
19+
);
20+
21+
context.inputs.push('extraTestInput12345');
22+
23+
const oldHandler: any = context.functionHandler;
24+
context.functionHandler = (...args) => {
25+
context.invocationContext.log('extra log from updated functionHandler');
26+
return oldHandler(...args);
27+
};
28+
29+
context.hookData.testData = 'preInvocationHookData123';
30+
31+
// Validate readonly properties
32+
try {
33+
// @ts-expect-error by-design
34+
context.hookData = {};
35+
} catch (err) {
36+
context.invocationContext.log(`Ignored error: ${err.message}`);
37+
}
38+
39+
try {
40+
// @ts-expect-error by-design
41+
context.invocationContext = {};
42+
} catch (err) {
43+
context.invocationContext.log(`Ignored error: ${err.message}`);
44+
}
45+
}
46+
});
47+
48+
const hookToDispose = app.hook.preInvocation((context) => {
49+
context.invocationContext.log('This should never run.');
50+
});
51+
hookToDispose.dispose();
52+
53+
app.hook.postInvocation((context) => {
54+
if (context.invocationContext.functionName === 'httpTriggerForHooks') {
55+
const resultString = JSON.stringify(context.result);
56+
context.invocationContext.log(
57+
`postInvocation hook executed with error: ${context.error}, result: ${resultString}, hook data: ${context.hookData.testData}`
58+
);
59+
}
60+
});
61+
62+
app.hook.postInvocation(async () => {
763
// Add slight delay to ensure logs show up before the invocation finishes
864
// See these issues for more info:
965
// https://github.com/Azure/azure-functions-host/issues/9238

src/hooks.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { expect } from 'chai';
5+
import { default as fetch } from 'node-fetch';
6+
import { getFuncUrl } from './constants';
7+
import { model, waitForOutput } from './global.test';
8+
9+
describe('hooks', () => {
10+
before(function (this: Mocha.Context) {
11+
if (model === 'v3') {
12+
this.skip();
13+
}
14+
});
15+
16+
it('app start', async () => {
17+
await waitForOutput(`Executing 1 "appStart" hooks`, { checkFullOutput: true });
18+
await waitForOutput(`appStart hook executed.`, { checkFullOutput: true });
19+
await waitForOutput(`Executed "appStart" hooks`, { checkFullOutput: true });
20+
});
21+
22+
it('invocation', async () => {
23+
const outputUrl = getFuncUrl('httpTriggerForHooks');
24+
25+
const response = await fetch(outputUrl, { method: 'POST' });
26+
expect(response.status).to.equal(200);
27+
const body = await response.text();
28+
expect(body).to.equal('hookBodyResponse');
29+
30+
// pre invocation
31+
await waitForOutput(`Executing 1 "preInvocation" hooks`);
32+
await waitForOutput(`preInvocation hook executed with inputs ["HttpRequest"]`);
33+
await waitForOutput(`Ignored error: Cannot assign to read only property 'hookData'`);
34+
await waitForOutput(`Ignored error: Cannot assign to read only property 'invocationContext'`);
35+
await waitForOutput(`Executed "preInvocation" hooks`);
36+
37+
// invocation
38+
await waitForOutput(`extra log from updated functionHandler`);
39+
await waitForOutput(`httpTriggerForHooks was triggered with second input extraTestInput12345`);
40+
41+
// post invocation
42+
await waitForOutput(`Executing 2 "postInvocation" hooks`);
43+
await waitForOutput(
44+
`postInvocation hook executed with error: null, result: {"body":"hookBodyResponse"}, hook data: preInvocationHookData123`
45+
);
46+
await waitForOutput(`Executed "postInvocation" hooks`);
47+
});
48+
});

0 commit comments

Comments
 (0)