1 | var assert = require('assert');
|
2 | var async = require('async');
|
3 | var sinon = require('sinon');
|
4 | var shortid = require('shortid');
|
5 | var _ = require('lodash');
|
6 | var path = require('path');
|
7 | var parseUrl = require('url').parse;
|
8 | var rimraf = require('rimraf');
|
9 | var log = require('../lib/log');
|
10 | var fs = require('fs');
|
11 | var childProcess = require('child_process');
|
12 | var request = require('request');
|
13 | var inquirer = require('inquirer');
|
14 | var createApp = require('../commands/create-app');
|
15 | var os = require('os');
|
16 | var debug = require('debug')('4front:cli:create-app-test');
|
17 | var mockSpawn = require('./mock-spawn');
|
18 | var mockInquirer = require('./mock-inquirer');
|
19 |
|
20 | require('dash-assert');
|
21 |
|
22 | var sampleTemplate = path.resolve(__dirname,
|
23 | "./fixtures/sample-app-template.zip");
|
24 | var sampleTemplateWithRoot = path.resolve(__dirname,
|
25 | "./fixtures/sample-app-with-root-dir.zip");
|
26 |
|
27 | describe('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 |
|
79 |
|
80 | return fs.createReadStream(sampleTemplate);
|
81 | case '/sample-app-with-root-dir.zip':
|
82 |
|
83 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
311 | assert.equal(self.mockInquirer.askedCount('appName'), 3);
|
312 |
|
313 | done();
|
314 | });
|
315 | });
|
316 | });
|