UNPKG

9 kBJavaScriptView Raw
1var assert = require('assert');
2var async = require('async');
3var sinon = require('sinon');
4var shortid = require('shortid');
5var _ = require('lodash');
6var path = require('path');
7var parseUrl = require('url').parse;
8var rimraf = require('rimraf');
9var log = require('../lib/log');
10var fs = require('fs');
11var childProcess = require('child_process');
12var request = require('request');
13var inquirer = require('inquirer');
14var createApp = require('../commands/create-app');
15var os = require('os');
16var debug = require('debug')('4front:cli:create-app-test');
17var mockSpawn = require('./mock-spawn');
18var mockInquirer = require('./mock-inquirer');
19
20require('dash-assert');
21
22var sampleTemplate = path.resolve(__dirname,
23 "./fixtures/sample-app-template.zip");
24var sampleTemplateWithRoot = path.resolve(__dirname,
25 "./fixtures/sample-app-with-root-dir.zip");
26
27describe('create-app', function() {
28 var self;
29
30 beforeEach(function(done) {
31 self = this;
32
33 this.program = {
34 profile: {
35 name: 'default',
36 endpoint: 'https://apphost.com/',
37 jwt: {
38 token: '23523454'
39 }
40 },
41 baseDir: os.tmpdir()
42 };
43
44 this.mockAnswers = {
45 appName: 'test-app',
46 endpoint: 'https://apphost.com',
47 username: 'username',
48 password: 'password'
49 };
50
51 this.mockInquirer = require('./mock-inquirer')(this.mockAnswers);
52 this.mockOrgs = [{
53 orgId: '1',
54 name: 'org 1'
55 }, {
56 orgId: '2',
57 name: 'org 2'
58 }];
59 this.mockTemplates = [{
60 name: 'template1',
61 url: 'http://github.com/repo.tar.gz'
62 }];
63
64 sinon.stub(log, 'write', _.noop());
65
66 sinon.stub(request, 'get', function(options, callback) {
67 var urlPath = parseUrl(options.url).pathname;
68 switch (urlPath) {
69 case '/api/platform/starter-templates':
70 return callback(null, {
71 statusCode: 200
72 }, self.mockTemplates);
73 case '/api/profile/orgs':
74 return callback(null, {
75 statusCode: 200
76 }, self.mockOrgs);
77 case '/sample-app-template.zip':
78 // Return the readStream directly. The way this call is made
79 // request is not passing in a callback.
80 return fs.createReadStream(sampleTemplate);
81 case '/sample-app-with-root-dir.zip':
82 // Return the readStream directly. The way this call is made
83 // request is not passing in a callback.
84 return fs.createReadStream(sampleTemplateWithRoot);
85
86 default:
87 throw new Error("Unexpected request " + urlPath);
88 }
89 });
90
91 sinon.stub(request, 'head').yields(null, {
92 statusCode: 404
93 });
94
95 sinon.stub(request, 'post', function(options, callback) {
96 // Mock the create app post request
97 if (options.url.indexOf('/apps')) {
98 callback(null, {
99 statusCode: 201
100 }, {
101 appId: options.json.appId,
102 name: options.json.name,
103 url: 'https://' + options.json.name + '.apphost.com'
104 });
105 }
106 });
107
108 sinon.stub(inquirer, 'prompt', this.mockInquirer.prompt);
109 sinon.stub(childProcess, 'spawn', mockSpawn);
110
111 rimraf(path.join(os.tmpdir(), 'test-app'), done);
112 });
113
114 afterEach(function() {
115 request.post.restore();
116 request.head.restore();
117 request.get.restore();
118 inquirer.prompt.restore();
119 log.write.restore();
120 childProcess.spawn.restore();
121 });
122
123 it('collect input', function(done) {
124 _.extend(this.mockAnswers, {
125 templateUrl: 'blank',
126 startingMode: 'scratch'
127 });
128
129 createApp(this.program, function(err) {
130 if (err) return done(err);
131
132 assert.isTrue(request.get.calledWith(
133 sinon.match({
134 url: 'https://apphost.com/api/profile/orgs'
135 })))
136
137 assert.isTrue(request.get.calledWith(
138 sinon.match({
139 url: 'https://apphost.com/api/platform/starter-templates'
140 })))
141
142 assert.isTrue(request.head.calledWith(
143 sinon.match({
144 url: 'https://apphost.com/api/apps/test-app'
145 })))
146
147 assert.isTrue(self.mockInquirer.wasAsked('orgId'))
148 assert.isTrue(self.mockInquirer.wasAsked('appName'));
149 assert.isTrue(self.mockInquirer.wasAsked('startingMode'));
150 assert.isTrue(self.mockInquirer.wasAsked('templateUrl'));
151 assert.isFalse(self.mockInquirer.wasAsked(
152 'confirmExistingDir'));
153 done();
154 });
155 });
156
157 describe('downloads starter template', function() {
158 it('uses command line template arg', function(done) {
159 _.extend(this.program, {
160 templateUrl: "http://github.com/sample-app-template.zip"
161 });
162
163 createApp(this.program, function(err, createdApp) {
164 if (err) return done(err);
165
166 var appDir = path.join(self.program.baseDir, self.mockAnswers
167 .appName);
168
169 assert.isFalse(self.mockInquirer.wasAsked(
170 'startingMode'));
171
172 assert.isTrue(request.get.calledWith(
173 sinon.match({
174 url: self.program.templateUrl
175 })));
176
177 // Assert that both npm and bower install were run
178 assert.isTrue(childProcess.spawn.calledWith('npm', [
179 'install'
180 ], sinon.match({
181 cwd: appDir
182 })));
183 assert.isTrue(childProcess.spawn.calledWith('bower', [
184 'install'
185 ], sinon.match({
186 cwd: appDir
187 })));
188
189 // verify that the extracted contents from the template zip are present
190 ['index.html', 'js/app.js', 'css/styles.css',
191 'package.json'
192 ].forEach(function(file) {
193 var filePath = path.join(appDir, file);
194 assert.isTrue(fs.existsSync(filePath));
195 });
196
197 var packageJson = JSON.parse(fs.readFileSync(path.join(
198 appDir, 'package.json')));
199 assert.equal(packageJson._virtualApp.appId, createdApp.appId);
200
201 done();
202 });
203 });
204
205 it('uses template specified at prompt', function(done) {
206 _.extend(this.mockAnswers, {
207 startingMode: 'scratch',
208 templateUrl: 'http://github.com/sample-app-template.zip'
209 });
210
211 createApp(this.program, function(err) {
212 if (err) return done(err);
213
214 assert.isTrue(self.mockInquirer.wasAsked('templateUrl'));
215 assert.isTrue(request.get.calledWith(
216 sinon.match({
217 url: self.mockAnswers.templateUrl
218 })));
219
220 done();
221 });
222 });
223
224 it('skips root directory in zip archive', function(done) {
225 _.extend(this.mockAnswers, {
226 startingMode: 'scratch',
227 templateUrl: 'http://github.com/sample-app-with-root-dir.zip'
228 });
229
230 createApp(this.program, function(err) {
231 if (err) return done(err);
232
233 var appDir = path.join(self.program.baseDir, self.mockAnswers
234 .appName);
235
236 // verify that the extracted contents from the template zip are present
237 ['index.html', 'js/app.js'].forEach(function(file) {
238 var filePath = path.join(appDir, 'app', file);
239
240 debug('check if %s exists', filePath);
241 assert.isTrue(fs.existsSync(filePath));
242 });
243
244 done();
245 });
246 });
247 });
248
249 it('returns error when app directory already exists', function(done) {
250 _.extend(this.mockAnswers, {
251 appName: shortid.generate(),
252 startingMode: 'scratch',
253 templateUrl: 'blank'
254 });
255
256 fs.mkdirSync(path.join(self.program.baseDir, this.mockAnswers.appName));
257
258 createApp(this.program, function(err) {
259 assert.isNotNull(err);
260 assert.ok(/already exists/.test(err));
261 done();
262 });
263 });
264
265 it('creates index.html when no template chosen', function(done) {
266 _.extend(this.mockAnswers, {
267 startingMode: 'scratch',
268 templateUrl: 'blank'
269 });
270
271 createApp(this.program, function(err, createdApp) {
272 if (err) return done(err);
273
274 var appDir = path.join(self.program.baseDir, self.mockAnswers
275 .appName);
276
277 ['index.html', 'package.json'].forEach(function(file) {
278 var filePath = path.join(appDir, file);
279 assert.isTrue(fs.existsSync(filePath));
280 });
281
282 // Look at the package.json
283 var packageJson = JSON.parse(fs.readFileSync(path.join(appDir,
284 'package.json')));
285 assert.isObject(packageJson._virtualApp);
286 assert.equal(packageJson._virtualApp.appId, createdApp.appId);
287
288 done();
289 });
290 });
291
292 it('continues to ask for appName until one available', function(done) {
293 request.head.restore();
294
295 var headStub = sinon.stub(request, 'head');
296 headStub.onCall(0).yields(null, {
297 statusCode: 200
298 });
299 headStub.onCall(1).yields(null, {
300 statusCode: 200
301 });
302 // On the 3rd call yield a 404 indicating the app name is available
303 headStub.onCall(2).yields(null, {
304 statusCode: 404
305 });
306
307 createApp(this.program, function(err) {
308 if (err) return done(err);
309
310 // The appName question should have been asked 3 times
311 assert.equal(self.mockInquirer.askedCount('appName'), 3);
312
313 done();
314 });
315 });
316});