Skip to content

Commit 28dee85

Browse files
committed
test(command-init): add tests
1 parent 95dc524 commit 28dee85

File tree

7 files changed

+324
-8
lines changed

7 files changed

+324
-8
lines changed

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@
192192
"standard-version": "^9.0.0",
193193
"strip-ansi": "^6.0.0",
194194
"temp-dir": "^2.0.0",
195+
"toml": "^3.0.0",
195196
"tomlify-j0.4": "^3.0.0"
196197
},
197198
"ava": {

src/utils/init/utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ const getPluginsToInstall = ({ plugins, installSinglePlugin, recommendedPlugins
111111

112112
const getBuildSettings = async ({ siteRoot, config, env, warn }) => {
113113
const nodeVersion = await detectNodeVersion({ siteRoot, env, warn })
114-
const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins } = await getFrameworkInfo({
114+
const { frameworkName, frameworkBuildCommand, frameworkBuildDir, frameworkPlugins = [] } = await getFrameworkInfo({
115115
siteRoot,
116116
nodeVersion,
117117
})

tests/command.init.test.js

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
const { Buffer } = require('buffer')
2+
3+
const test = require('ava')
4+
const execa = require('execa')
5+
const toml = require('toml')
6+
7+
const { readFileAsync } = require('../src/lib/fs')
8+
9+
const cliPath = require('./utils/cli-path')
10+
const { withMockApi } = require('./utils/mock-api')
11+
const { withSiteBuilder } = require('./utils/site-builder')
12+
13+
const handleQuestions = (process, questions) => {
14+
const remainingQuestions = [...questions]
15+
let buffer = ''
16+
process.stdout.on('data', (data) => {
17+
buffer += data.toString()
18+
const index = remainingQuestions.findIndex(({ question }) => buffer.includes(question))
19+
if (index >= 0) {
20+
buffer = ''
21+
process.stdin.write(Buffer.from(remainingQuestions[index].answer))
22+
remainingQuestions.splice(index, 1)
23+
}
24+
})
25+
}
26+
27+
const CONFIRM = '\n'
28+
const DOWN = '\u001B[B'
29+
const answerWithValue = (value) => `${value}${CONFIRM}`
30+
31+
const assertSiteInit = async (
32+
t,
33+
builder,
34+
requests,
35+
{ command = 'custom-build-command', functions = 'custom-functions', publish = 'custom-publish', plugins = [] } = {},
36+
) => {
37+
// assert netlify.toml was created with user inputs
38+
const netlifyToml = toml.parse(await readFileAsync(`${builder.directory}/netlify.toml`, 'utf8'))
39+
t.deepEqual(netlifyToml, {
40+
build: { command, functions, publish },
41+
...(plugins.length === 0 ? {} : { plugins }),
42+
})
43+
44+
// assert updateSite was called with user inputs
45+
const siteUpdateRequests = requests.filter(({ path }) => path === '/api/v1/sites/site_id').map(({ body }) => body)
46+
t.deepEqual(siteUpdateRequests, [
47+
{
48+
plugins,
49+
repo: {
50+
allowed_branches: ['master'],
51+
cmd: command,
52+
dir: publish,
53+
provider: 'manual',
54+
repo_branch: 'master',
55+
repo_path: '[email protected]:owner/repo.git',
56+
},
57+
},
58+
{
59+
build_settings: {
60+
functions_dir: functions,
61+
},
62+
},
63+
])
64+
}
65+
66+
test('netlify init existing site', async (t) => {
67+
const initQuestions = [
68+
{
69+
question: 'Create & configure a new site',
70+
answer: CONFIRM,
71+
},
72+
{
73+
question: 'How do you want to link this folder to a site',
74+
answer: CONFIRM,
75+
},
76+
{
77+
question: 'Your build command (hugo build/yarn run build/etc)',
78+
answer: answerWithValue('custom-build-command'),
79+
},
80+
{
81+
question: 'Directory to deploy (blank for current dir)',
82+
answer: answerWithValue('custom-publish'),
83+
},
84+
{
85+
question: 'Netlify functions folder',
86+
answer: answerWithValue('custom-functions'),
87+
},
88+
{
89+
question: 'No netlify.toml detected',
90+
answer: CONFIRM,
91+
},
92+
{ question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM },
93+
{ question: 'The SSH URL of the remote git repo', answer: CONFIRM },
94+
{ question: 'Configure the following webhook for your repository', answer: CONFIRM },
95+
]
96+
97+
const routes = [
98+
{
99+
path: 'sites',
100+
response: [
101+
{
102+
admin_url: 'https://app.netlify.com/sites/site-name/overview',
103+
ssl_url: 'https://site-name.netlify.app/',
104+
id: 'site_id',
105+
name: 'site-name',
106+
build_settings: { repo_url: 'https://github.com/owner/repo' },
107+
},
108+
],
109+
},
110+
{ path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } },
111+
{ path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } },
112+
]
113+
114+
await withSiteBuilder('new-site', async (builder) => {
115+
builder.withGit({ repoUrl: '[email protected]:owner/repo.git' })
116+
117+
await builder.buildAsync()
118+
await withMockApi(routes, async ({ apiUrl, requests }) => {
119+
// --force is required since we we return an existing site in the `sites` route
120+
// --manual is used to avoid the config-github flow that uses GitHub API
121+
const childProcess = execa(cliPath, ['init', '--force', '--manual'], {
122+
cwd: builder.directory,
123+
env: { NETLIFY_API_URL: apiUrl },
124+
})
125+
126+
handleQuestions(childProcess, initQuestions)
127+
128+
await childProcess
129+
130+
await assertSiteInit(t, builder, requests)
131+
})
132+
})
133+
})
134+
135+
test('netlify init new site', async (t) => {
136+
const initQuestions = [
137+
{
138+
question: 'Create & configure a new site',
139+
answer: answerWithValue(DOWN),
140+
},
141+
{ question: 'Team: (Use arrow keys)', answer: CONFIRM },
142+
{ question: 'Site name (optional)', answer: answerWithValue('test-site-name') },
143+
{
144+
question: 'Your build command (hugo build/yarn run build/etc)',
145+
answer: answerWithValue('custom-build-command'),
146+
},
147+
{
148+
question: 'Directory to deploy (blank for current dir)',
149+
answer: answerWithValue('custom-publish'),
150+
},
151+
{
152+
question: 'Netlify functions folder',
153+
answer: answerWithValue('custom-functions'),
154+
},
155+
{
156+
question: 'No netlify.toml detected',
157+
answer: CONFIRM,
158+
},
159+
{ question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM },
160+
{ question: 'The SSH URL of the remote git repo', answer: CONFIRM },
161+
{ question: 'Configure the following webhook for your repository', answer: CONFIRM },
162+
]
163+
164+
const routes = [
165+
{
166+
path: 'accounts',
167+
response: [{ slug: 'test-account' }],
168+
},
169+
{
170+
path: 'sites',
171+
response: [],
172+
},
173+
{
174+
path: 'user',
175+
response: { name: 'test user', slug: 'test-user', email: '[email protected]' },
176+
},
177+
{
178+
path: 'test-account/sites',
179+
method: 'post',
180+
response: { id: 'site_id', name: 'test-site-name' },
181+
},
182+
{ path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } },
183+
{ path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } },
184+
]
185+
186+
await withSiteBuilder('new-site', async (builder) => {
187+
builder.withGit({ repoUrl: '[email protected]:owner/repo.git' })
188+
189+
await builder.buildAsync()
190+
await withMockApi(routes, async ({ apiUrl, requests }) => {
191+
// --manual is used to avoid the config-github flow that uses GitHub API
192+
const childProcess = execa(cliPath, ['init', '--manual'], {
193+
cwd: builder.directory,
194+
env: { NETLIFY_API_URL: apiUrl },
195+
})
196+
197+
handleQuestions(childProcess, initQuestions)
198+
199+
await childProcess
200+
201+
await assertSiteInit(t, builder, requests)
202+
})
203+
})
204+
})
205+
206+
test('netlify init new Next.js site', async (t) => {
207+
const initQuestions = [
208+
{
209+
question: 'Create & configure a new site',
210+
answer: answerWithValue(DOWN),
211+
},
212+
{ question: 'Team: (Use arrow keys)', answer: CONFIRM },
213+
{ question: 'Site name (optional)', answer: answerWithValue('test-site-name') },
214+
{
215+
question: 'Your build command (hugo build/yarn run build/etc)',
216+
answer: answerWithValue('custom-build-command'),
217+
},
218+
{
219+
question: 'Directory to deploy (blank for current dir)',
220+
answer: answerWithValue('custom-publish'),
221+
},
222+
{
223+
question: 'Netlify functions folder',
224+
answer: answerWithValue('custom-functions'),
225+
},
226+
{
227+
question: 'Install Next on Netlify plugin',
228+
answer: CONFIRM,
229+
},
230+
{
231+
question: 'No netlify.toml detected',
232+
answer: CONFIRM,
233+
},
234+
{ question: 'Give this Netlify SSH public key access to your repository', answer: CONFIRM },
235+
{ question: 'The SSH URL of the remote git repo', answer: CONFIRM },
236+
{ question: 'Configure the following webhook for your repository', answer: CONFIRM },
237+
]
238+
239+
const routes = [
240+
{
241+
path: 'accounts',
242+
response: [{ slug: 'test-account' }],
243+
},
244+
245+
{
246+
path: 'sites',
247+
response: [],
248+
},
249+
{
250+
path: 'user',
251+
response: { name: 'test user', slug: 'test-user', email: '[email protected]' },
252+
},
253+
{
254+
path: 'test-account/sites',
255+
method: 'post',
256+
response: { id: 'site_id', name: 'test-site-name' },
257+
},
258+
{ path: 'deploy_keys', method: 'post', response: { public_key: 'public_key' } },
259+
{ path: 'sites/site_id', method: 'patch', response: { deploy_hook: 'deploy_hook' } },
260+
]
261+
262+
await withSiteBuilder('new-site', async (builder) => {
263+
builder
264+
.withGit({ repoUrl: '[email protected]:owner/repo.git' })
265+
.withPackageJson({ packageJson: { dependencies: { next: '^10.0.0' } } })
266+
267+
await builder.buildAsync()
268+
await withMockApi(routes, async ({ apiUrl, requests }) => {
269+
// --manual is used to avoid the config-github flow that uses GitHub API
270+
const childProcess = execa(cliPath, ['init', '--manual'], {
271+
cwd: builder.directory,
272+
env: { NETLIFY_API_URL: apiUrl },
273+
})
274+
275+
handleQuestions(childProcess, initQuestions)
276+
277+
await childProcess
278+
279+
await assertSiteInit(t, builder, requests, { plugins: [{ package: '@netlify/plugin-nextjs' }] })
280+
})
281+
})
282+
})

tests/command.lm.test.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ if (process.env.IS_FORK !== 'true') {
2121
const builder = createSiteBuilder({ siteName: 'site-with-lm' })
2222
await builder.buildAsync()
2323

24-
const mockApi = startMockApi({
24+
const { server } = startMockApi({
2525
routes: [
2626
{ method: 'post', path: 'sites/site_id/services/large-media/instances', status: 201 },
2727
{ path: 'sites/site_id', response: { id_domain: 'localhost' } },
@@ -33,8 +33,8 @@ if (process.env.IS_FORK !== 'true') {
3333
env: { NETLIFY_SITE_ID: siteId, SHELL: process.env.SHELL || 'bash' },
3434
}
3535
t.context.builder = builder
36-
t.context.mockApi = mockApi
37-
t.context.apiUrl = `http://localhost:${mockApi.address().port}/api/v1`
36+
t.context.mockApi = server
37+
t.context.apiUrl = `http://localhost:${server.address().port}/api/v1`
3838

3939
await callCli(['lm:uninstall'], t.context.execOptions)
4040
})

tests/utils/mock-api.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,48 @@
11
const bodyParser = require('body-parser')
22
const express = require('express')
33

4+
const addRequest = (requests, request) => {
5+
requests.push({
6+
path: request.path,
7+
body: request.body,
8+
method: request.method,
9+
})
10+
}
11+
412
const startMockApi = ({ routes }) => {
13+
const requests = []
514
const app = express()
615
app.use(bodyParser.urlencoded({ extended: true }))
16+
app.use(bodyParser.json())
17+
app.use(bodyParser.raw())
718

819
routes.forEach(({ method = 'get', path, response = {}, status = 200 }) => {
920
app[method.toLowerCase()](`/api/v1/${path}`, function onRequest(req, res) {
21+
addRequest(requests, req)
1022
res.status(status)
1123
res.json(response)
1224
})
1325
})
1426

1527
app.all('*', function onRequest(req, res) {
28+
addRequest(requests, req)
1629
console.warn(`Route not found: ${req.url}`)
1730
res.status(404)
1831
res.json({ message: 'Not found' })
1932
})
2033

21-
return app.listen()
34+
return { server: app.listen(), requests }
35+
}
36+
37+
const withMockApi = async (routes, testHandler) => {
38+
let mockApi
39+
try {
40+
mockApi = startMockApi({ routes })
41+
const apiUrl = `http://localhost:${mockApi.server.address().port}/api/v1`
42+
return await testHandler({ apiUrl, requests: mockApi.requests })
43+
} finally {
44+
mockApi.server.close()
45+
}
2246
}
2347

24-
module.exports = { startMockApi }
48+
module.exports = { withMockApi, startMockApi }

0 commit comments

Comments
 (0)