Skip to content

Commit

Permalink
fix(command-dev): handle event triggered background functions (#2704)
Browse files Browse the repository at this point in the history
  • Loading branch information
erezrokah committed Jun 16, 2021
1 parent b55dd2b commit 564e492
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 8 deletions.
7 changes: 4 additions & 3 deletions src/commands/functions/invoke.js
Expand Up @@ -8,11 +8,11 @@ const inquirer = require('inquirer')
const fetch = require('node-fetch')

const Command = require('../../utils/command')
const { getFunctions } = require('../../utils/get-functions')
const { getFunctions, BACKGROUND } = require('../../utils/get-functions')
const { NETLIFYDEVWARN } = require('../../utils/logo')

// https://www.netlify.com/docs/functions/#event-triggered-functions
const eventTriggeredFunctions = new Set([
const events = [
'deploy-building',
'deploy-succeeded',
'deploy-failed',
Expand All @@ -25,7 +25,8 @@ const eventTriggeredFunctions = new Set([
'identity-validate',
'identity-signup',
'identity-login',
])
]
const eventTriggeredFunctions = new Set([...events, ...events.map((name) => `${name}${BACKGROUND}`)])

const DEFAULT_PORT = 8888

Expand Down
2 changes: 1 addition & 1 deletion src/utils/get-functions.js
Expand Up @@ -44,4 +44,4 @@ const getFunctionsAndWatchDirs = async (functionsSrcDir) => {
return { functions: functionsWithProps, watchDirs }
}

module.exports = { getFunctions, getFunctionsAndWatchDirs }
module.exports = { getFunctions, getFunctionsAndWatchDirs, BACKGROUND }
31 changes: 27 additions & 4 deletions src/utils/serve-functions.js
Expand Up @@ -21,7 +21,7 @@ const winston = require('winston')
const { getLogMessage } = require('../lib/log')

const { detectFunctionsBuilder } = require('./detect-functions-builder')
const { getFunctionsAndWatchDirs } = require('./get-functions')
const { getFunctionsAndWatchDirs, BACKGROUND } = require('./get-functions')
const { NETLIFYDEVLOG, NETLIFYDEVERR } = require('./logo')

const formatLambdaLocalError = (err) => `${err.errorType}: ${err.errorMessage}\n ${err.stackTrace.join('\n ')}`
Expand Down Expand Up @@ -322,7 +322,25 @@ const setupDefaultFunctionHandler = async ({ capabilities, directory, warn }) =>
return { getFunctionByName }
}

const createFormSubmissionHandler = function ({ siteUrl, warn }) {
const getFormHandler = function ({ getFunctionByName, warn }) {
const handlers = ['submission-created', `submission-created${BACKGROUND}`]
.map((name) => getFunctionByName(name))
.filter(Boolean)
.map(({ name }) => name)

if (handlers.length === 0) {
warn(`Missing form submission function handler`)
return
}

if (handlers.length === 2) {
warn(`Detected both '${handlers[0]}' and '${handlers[1]}' form submission functions handlers, using ${handlers[0]}`)
}

return handlers[0]
}

const createFormSubmissionHandler = function ({ getFunctionByName, siteUrl, warn }) {
return async function formSubmissionHandler(req, res, next) {
if (req.url.startsWith('/.netlify/') || req.method !== 'POST') return next()

Expand All @@ -334,8 +352,13 @@ const createFormSubmissionHandler = function ({ siteUrl, warn }) {
})
fakeRequest.headers = req.headers

const handlerName = getFormHandler({ getFunctionByName, warn })
if (!handlerName) {
return next()
}

const originalUrl = new URL(req.url, 'http://localhost')
req.url = `/.netlify/functions/submission-created${originalUrl.search}`
req.url = `/.netlify/functions/${handlerName}${originalUrl.search}`

const ct = parseContentType(req)
let fields = {}
Expand Down Expand Up @@ -450,7 +473,7 @@ const getFunctionsServer = async function ({ getFunctionByName, siteUrl, warn, t
}),
)
app.use(bodyParser.raw({ limit: '6mb', type: '*/*' }))
app.use(createFormSubmissionHandler({ siteUrl, warn }))
app.use(createFormSubmissionHandler({ getFunctionByName, siteUrl, warn }))
app.use(
expressLogging(console, {
blacklist: ['/favicon.ico'],
Expand Down
33 changes: 33 additions & 0 deletions tests/command.dev.test.js
Expand Up @@ -694,6 +694,39 @@ testMatrix.forEach(({ args }) => {
})
})

test(testName('should handle form submission with a background function', args), async (t) => {
await withSiteBuilder('site-with-form-background-function', async (builder) => {
await builder
.withContentFile({
path: 'index.html',
content: '<h1>⊂◉‿◉つ</h1>',
})
.withNetlifyToml({
config: {
functions: { directory: 'functions' },
},
})
.withFunction({
path: 'submission-created-background.js',
handler: async (event) => ({
statusCode: 200,
body: JSON.stringify(event),
}),
})
.buildAsync()

await withDevServer({ cwd: builder.directory, args }, async (server) => {
const form = new FormData()
form.append('some', 'thing')
const response = await got.post(`${server.url}/?ding=dong`, {
body: form,
})
t.is(response.statusCode, 202)
t.is(response.body, '')
})
})
})

test(testName('should not handle form submission when content type is `text/plain`', args), async (t) => {
await withSiteBuilder('site-with-form-text-plain', async (builder) => {
builder
Expand Down
26 changes: 26 additions & 0 deletions tests/command.functions.test.js
Expand Up @@ -247,4 +247,30 @@ test('should use settings from netlify.toml dev', async (t) => {
})
})

test('should trigger background function from event', async (t) => {
await withSiteBuilder('site-with-ping-function', async (builder) => {
await builder
.withNetlifyToml({ config: { functions: { directory: 'functions' } } })
.withFunction({
path: 'identity-validate-background.js',
handler: async (event) => ({
statusCode: 200,
body: JSON.stringify(event.body),
}),
})
.buildAsync()

await withDevServer({ cwd: builder.directory }, async (server) => {
const stdout = await callCli(
['functions:invoke', 'identity-validate-background', '--identity', `--port=${server.port}`],
{
cwd: builder.directory,
},
)
// background functions always return an empty response
t.is(stdout, '')
})
})
})

/* eslint-enable require-await */

0 comments on commit 564e492

Please sign in to comment.