Skip to content

Commit c56c957

Browse files
committed
chore: add tests, refactor code a bit to make it a tad more testable.
1 parent 2112e2a commit c56c957

File tree

8 files changed

+313
-207
lines changed

8 files changed

+313
-207
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
runs-on: ${{ matrix.os }}
1616
strategy:
1717
matrix:
18-
node-version: [ 16.x, 18.x, 20.x ]
18+
node-version: [ 18.x, 20.x, 22.x ]
1919
os: [ windows-latest, ubuntu-latest, macOS-latest ]
2020

2121
# Go
@@ -41,7 +41,6 @@ jobs:
4141
run: npm install
4242

4343
- name: Test
44-
if: matrix.node-version > '16.x'
4544
run: npm test
4645
env:
4746
CI: true

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
scratch/
22
node_modules
33
.vscode
4+
.nyc_output
5+
coverage

jsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"module": "commonJS",
4+
"moduleResolution": "node",
5+
"target": "es6"
6+
},
7+
"include": ["src/**/*"]
8+
}

package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
"description": "Interactively invoke Lambdas in Architect Sandbox with arbitrary events",
55
"main": "src/index.js",
66
"scripts": {
7-
"test": "npm run lint",
7+
"coverage": "nyc --reporter=lcov --reporter=text npm run test:unit",
8+
"test": "npm run lint && npm run coverage",
9+
"test:unit": "cross-env tape 'test/unit/**/*-test.js' | tap-arc",
810
"lint": "eslint . --fix",
911
"rc": "npm version prerelease --preid RC"
1012
},
@@ -31,7 +33,12 @@
3133
},
3234
"devDependencies": {
3335
"@architect/eslint-config": "^3.0.0",
34-
"eslint": "^9.2.0"
36+
"cross-env": "^7.0.3",
37+
"eslint": "^9.2.0",
38+
"nyc": "^17.1.0",
39+
"proxyquire": "^2.1.3",
40+
"tap-arc": "^1.3.2",
41+
"tape": "^5.9.0"
3542
},
3643
"keywords": [
3744
"arc",

src/index.js

Lines changed: 5 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,23 @@
1-
let { join } = require('path')
2-
let { existsSync, readFileSync } = require('fs')
31
let { updater } = require('@architect/utils')
4-
let { prompt } = require('enquirer')
5-
let colors = require('ansi-colors')
62
let update = updater('Invoker')
7-
let mock = require('./event-mocks')
8-
let { marshall } = require('@aws-sdk/util-dynamodb')
3+
let bindInputHandler = require('./input-handler')
4+
let { start, end, defaultPragmas } = require('./utils')
95
let deactivatedInvoke = async () => console.log('Sandbox not yet started!')
106

11-
let lastInvoke
12-
137
let sandbox = {
148
start: async ({ inventory: { inv }, invoke }) => {
159
start()
10+
let pragmas = defaultPragmas.slice(0)
1611
update.status(`Event invoker started, select an event to invoke by pressing 'i'`)
1712
plugin.invoke = invoke
18-
let { cwd, preferences } = inv._project
19-
let jsonMocks = join(cwd, 'sandbox-invoke-mocks.json')
20-
let jsMocks = join(cwd, 'sandbox-invoke-mocks.js')
21-
let cjsMocks = join(cwd, 'sandbox-invoke-mocks.cjs')
22-
let mjsMocks = join(cwd, 'sandbox-invoke-mocks.mjs')
23-
24-
let pragmas = [ 'customLambdas', 'events', 'queues', 'scheduled', 'tables-streams' ]
13+
let { preferences } = inv._project
2514
let prefs = preferences?.sandbox?.invoker
2615
if (prefs) {
2716
if (Array.isArray(prefs)) pragmas = prefs
2817
else if (typeof prefs === 'string') pragmas = [ prefs ]
2918
else throw Error('Invalid @architect/plugin-lambda-invoker plugin preferences')
3019
}
31-
32-
process.stdin.on('data', async function eventInvokeListener (input) {
33-
// Build out the available event list each time to prevent caching
34-
let events = {}
35-
pragmas.forEach(pragma => {
36-
if (inv[pragma]) inv[pragma].forEach(({ name }) => {
37-
events[`@${pragma} ${name}`] = { pragma, name }
38-
})
39-
})
40-
// Add a cancel option should one desire
41-
events.cancel = ''
42-
43-
let lastEventName
44-
if (lastInvoke) {
45-
lastEventName = `Last invoke: @${lastInvoke.pragma} ${lastInvoke.name} (${lastInvoke.mockName})`
46-
events[lastEventName] = lastInvoke
47-
}
48-
49-
start()
50-
input = String(input)
51-
// Reset Enquirer's styles
52-
let options = {
53-
prefix: colors.white(colors.symbols?.question ?? '?'),
54-
styles: {
55-
em: colors.cyan, // Clear underlines
56-
danger: colors.red,
57-
strong: colors.white,
58-
},
59-
}
60-
if (input === 'i') {
61-
if (Object.keys(events).length === 1) {
62-
let none = 'No Lambdas found to invoke'
63-
if (pragmas.length) update.status(none, `Using the following pragmas: @${pragmas.join(', @')}`)
64-
else update.status(none)
65-
return
66-
}
67-
68-
let userPayload = {}
69-
let mockName = 'empty'
70-
let pragma, name, mocks, skipSelection
71-
72-
// Load invocation mocks
73-
/**/ if (existsSync(jsonMocks)) {
74-
mocks = JSON.parse(readFileSync(jsonMocks))
75-
}
76-
else if (existsSync(jsMocks)) {
77-
mocks = await getMod(jsMocks)
78-
}
79-
else if (existsSync(cjsMocks)) {
80-
// eslint-disable-next-line
81-
mocks = require(cjsMocks)
82-
}
83-
else if (existsSync(mjsMocks)) {
84-
mocks = await getMod(mjsMocks)
85-
}
86-
87-
try {
88-
let { lambda } = await prompt({
89-
type: 'select',
90-
name: 'lambda',
91-
numbered: true,
92-
message: 'Which event do you want to invoke?',
93-
hint: '\nYou can use numbers to change your selection',
94-
choices: Object.keys(events),
95-
}, options)
96-
if (lambda === 'cancel') return start()
97-
else if (lambda === lastEventName) {
98-
skipSelection = true
99-
var event = events[lastEventName]
100-
mockName = event.mockName
101-
}
102-
else {
103-
var event = events[lambda]
104-
}
105-
pragma = event.pragma
106-
name = event.name
107-
108-
// Set up non-cached user payload from last event
109-
if (lambda === lastEventName) {
110-
userPayload = mocks[pragma][name][mockName] || {}
111-
}
112-
}
113-
catch {
114-
update.status('Canceled invoke')
115-
return start()
116-
}
117-
118-
// Present options for mocks (if any)
119-
let mockable = ![ 'scheduled', 'tables-streams' ].includes(pragma)
120-
if (mocks?.[pragma]?.[name] && mockable && !skipSelection) {
121-
let selection = await prompt({
122-
type: 'select',
123-
name: 'mock',
124-
numbered: true,
125-
message: 'Which mock do you want to invoke?',
126-
choices: [ ...Object.keys(mocks[pragma][name]), 'empty' ],
127-
}, options)
128-
mockName = selection.mock
129-
userPayload = mocks[pragma][name][mockName] || {}
130-
}
131-
lastInvoke = { pragma, name, mockName, userPayload }
132-
133-
let payload
134-
/**/ if (pragma === 'events') payload = mock.events(userPayload)
135-
else if (pragma === 'queues') payload = mock.queues(userPayload)
136-
else if (pragma === 'scheduled') payload = mock.scheduled()
137-
else if (pragma === 'customLambdas') payload = mock.customLambdas(userPayload)
138-
else if (pragma === 'tables-streams') {
139-
let { eventName } = await prompt({
140-
type: 'select',
141-
name: 'eventName',
142-
message: 'Which kind of Dynamo Stream event do you want to invoke?',
143-
choices: [ 'INSERT', 'MODIFY', 'REMOVE' ],
144-
})
145-
payload = mock.tablesStreams(eventName, marshallJson(mocks?.[pragma]?.[name]?.[eventName]))
146-
}
147-
else {
148-
if (!Object.keys(userPayload).length) {
149-
update.warning('Warning: real AWS event sources generally do not emit empty payloads')
150-
}
151-
payload = userPayload
152-
}
153-
154-
// Wrap it up and invoke!
155-
let msg = `Invoking @${pragma} ${name}`
156-
msg += ` with ${mockName === 'empty' ? 'empty' : `'${mockName}'`} payload`
157-
update.status(msg)
158-
await invoke({ pragma, name, payload })
159-
start()
160-
}
161-
})
20+
process.stdin.on('data', bindInputHandler(update, pragmas, inv, invoke))
16221
},
16322
end: async () => {
16423
// Only remove our listener; removing Enquirer causes funky behavior
@@ -178,60 +37,3 @@ let plugin = {
17837
}
17938
module.exports = plugin
18039

181-
// Necessary per Enquirer #326
182-
function start () {
183-
if (process.stdin.isTTY) {
184-
process.stdin.setRawMode(true)
185-
process.stdin.setEncoding('utf8')
186-
process.stdin.resume()
187-
}
188-
}
189-
190-
// Super important to pause stdin, or Sandbox will hang forever in tests
191-
function end () {
192-
if (process.stdin.isTTY) {
193-
process.stdin.pause()
194-
}
195-
}
196-
197-
198-
let esmErrors = [
199-
'Cannot use import statement outside a module',
200-
`Unexpected token 'export'`,
201-
'require() of ES Module',
202-
'Must use import to load ES Module',
203-
]
204-
let hasEsmError = err => esmErrors.some(msg => err.message.includes(msg))
205-
async function getMod (filepath) {
206-
let mod
207-
208-
// Best effort to ensure changes to mocks are always reflected
209-
delete require.cache[require.resolve(filepath)]
210-
211-
try {
212-
mod = require(filepath)
213-
}
214-
catch (err) {
215-
if (hasEsmError(err)) {
216-
let path = process.platform.startsWith('win')
217-
? 'file://' + filepath
218-
: filepath
219-
let imported = await import(path)
220-
mod = imported.default ? imported.default : imported
221-
}
222-
else {
223-
throw err
224-
}
225-
}
226-
227-
return mod
228-
}
229-
// Marshalls Json from the mock into keys and newimage for tables-streams
230-
function marshallJson (json) {
231-
const marshalled = marshall(json)
232-
const Keys = Object.keys(marshalled).reduce((keys, key) => {
233-
keys[key] = { [Object.keys(marshalled[key])[0]]: true }
234-
return keys
235-
}, {})
236-
return { Keys, NewImage: marshalled }
237-
}

0 commit comments

Comments
 (0)