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, "./fixtures/sample-app-template.zip");
|
23 | var sampleTemplateWithRoot = path.resolve(__dirname, "./fixtures/sample-app-with-root-dir.zip");
|
24 |
|
25 | describe('create-app', function() {
|
26 | var self;
|
27 |
|
28 | beforeEach(function(done) {
|
29 | self = this;
|
30 |
|
31 | this.program = {
|
32 | profile: {
|
33 | name: 'default',
|
34 | endpoint: 'https://apphost.com/',
|
35 | jwt: {
|
36 | token: '23523454'
|
37 | }
|
38 | },
|
39 | baseDir: os.tmpdir()
|
40 | };
|
41 |
|
42 | this.mockAnswers = {
|
43 | appName: 'test-app',
|
44 | endpoint: 'https://apphost.com',
|
45 | username: 'username',
|
46 | password: 'password'
|
47 | };
|
48 |
|
49 | this.mockInquirer = require('./mock-inquirer')(this.mockAnswers);
|
50 | this.mockOrgs = [{orgId: '1', name: 'org 1'}, {orgId: '2', name: 'org 2'}];
|
51 | this.mockTemplates = [{name: 'template1', url:'http://github.com/repo.tar.gz'}];
|
52 |
|
53 | sinon.stub(log, 'write', _.noop());
|
54 |
|
55 | sinon.stub(request, 'get', function(options, callback) {
|
56 | var urlPath = parseUrl(options.url).pathname;
|
57 | switch (urlPath) {
|
58 | case '/api/platform/starter-templates':
|
59 | return callback(null, {statusCode: 200}, self.mockTemplates);
|
60 | break;
|
61 | case '/api/profile/orgs':
|
62 | return callback(null, {statusCode: 200}, self.mockOrgs);
|
63 | break;
|
64 | case '/sample-app-template.zip':
|
65 |
|
66 |
|
67 | return fs.createReadStream(sampleTemplate);
|
68 | break;
|
69 | case '/sample-app-with-root-dir.zip':
|
70 |
|
71 |
|
72 | return fs.createReadStream(sampleTemplateWithRoot);
|
73 |
|
74 | default:
|
75 | throw new Error("Unexpected request " + urlPath);
|
76 | }
|
77 | });
|
78 |
|
79 | sinon.stub(request, 'head').yields(null, {statusCode:404});
|
80 |
|
81 | sinon.stub(request, 'post', function(options, callback) {
|
82 |
|
83 | if (options.url.indexOf('/apps')) {
|
84 | callback(null, {statusCode: 201}, {
|
85 | appId: options.json.appId,
|
86 | name: options.json.name,
|
87 | url: 'https://' + options.json.name + '.apphost.com'
|
88 | });
|
89 | }
|
90 | });
|
91 |
|
92 | sinon.stub(inquirer, 'prompt', this.mockInquirer.prompt);
|
93 | sinon.stub(childProcess, 'spawn', mockSpawn);
|
94 |
|
95 | rimraf(path.join(os.tmpdir(), 'test-app'), done);
|
96 | });
|
97 |
|
98 | afterEach(function() {
|
99 | request.post.restore();
|
100 | request.head.restore();
|
101 | request.get.restore();
|
102 | inquirer.prompt.restore();
|
103 | log.write.restore();
|
104 | childProcess.spawn.restore();
|
105 | });
|
106 |
|
107 | it('collect input', function(done) {
|
108 | _.extend(this.mockAnswers, {
|
109 | templateUrl: 'blank',
|
110 | startingMode: 'scratch'
|
111 | });
|
112 |
|
113 | createApp(this.program, function(err) {
|
114 | if (err) return done(err);
|
115 |
|
116 | assert.isTrue(request.get.calledWith(
|
117 | sinon.match({url: 'https://apphost.com/api/profile/orgs'})))
|
118 |
|
119 | assert.isTrue(request.get.calledWith(
|
120 | sinon.match({url: 'https://apphost.com/api/platform/starter-templates'})))
|
121 |
|
122 | assert.isTrue(request.head.calledWith(
|
123 | sinon.match({url: 'https://apphost.com/api/apps/test-app'})))
|
124 |
|
125 | assert.isTrue(self.mockInquirer.wasAsked('orgId'))
|
126 | assert.isTrue(self.mockInquirer.wasAsked('appName'));
|
127 | assert.isTrue(self.mockInquirer.wasAsked('startingMode'));
|
128 | assert.isTrue(self.mockInquirer.wasAsked('templateUrl'));
|
129 | assert.isFalse(self.mockInquirer.wasAsked('confirmExistingDir'));
|
130 | done();
|
131 | });
|
132 | });
|
133 |
|
134 | describe('downloads starter template', function() {
|
135 | it('uses command line template arg', function(done) {
|
136 | _.extend(this.program, {
|
137 | templateUrl: "http://github.com/sample-app-template.zip"
|
138 | });
|
139 |
|
140 | createApp(this.program, function(err, createdApp) {
|
141 | if (err) return done(err);
|
142 |
|
143 | var appDir = path.join(self.program.baseDir, self.mockAnswers.appName);
|
144 |
|
145 | assert.isFalse(self.mockInquirer.wasAsked('startingMode'));
|
146 |
|
147 | assert.isTrue(request.get.calledWith(
|
148 | sinon.match({url: self.program.templateUrl})));
|
149 |
|
150 |
|
151 | assert.isTrue(childProcess.spawn.calledWith('npm', ['install'], sinon.match({cwd: appDir})));
|
152 | assert.isTrue(childProcess.spawn.calledWith('bower', ['install'], sinon.match({cwd: appDir})));
|
153 |
|
154 |
|
155 | ['index.html', 'js/app.js', 'css/styles.css', 'package.json'].forEach(function(file) {
|
156 | var filePath = path.join(appDir, file);
|
157 | assert.isTrue(fs.existsSync(filePath));
|
158 | });
|
159 |
|
160 | var packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json')));
|
161 | assert.equal(packageJson['_virtualApp'].appId, createdApp.appId);
|
162 |
|
163 | done();
|
164 | });
|
165 | });
|
166 |
|
167 | it('uses template specified at prompt', function(done) {
|
168 | _.extend(this.mockAnswers, {
|
169 | startingMode: 'scratch',
|
170 | templateUrl: 'http://github.com/sample-app-template.zip'
|
171 | });
|
172 |
|
173 | createApp(this.program, function(err) {
|
174 | if (err) return done(err);
|
175 |
|
176 | assert.isTrue(self.mockInquirer.wasAsked('templateUrl'));
|
177 | assert.isTrue(request.get.calledWith(
|
178 | sinon.match({url: self.mockAnswers.templateUrl})));
|
179 |
|
180 | done();
|
181 | });
|
182 | });
|
183 |
|
184 | it('skips root directory in zip archive', function(done) {
|
185 | _.extend(this.mockAnswers, {
|
186 | startingMode: 'scratch',
|
187 | templateUrl: 'http://github.com/sample-app-with-root-dir.zip'
|
188 | });
|
189 |
|
190 | createApp(this.program, function(err) {
|
191 | if (err) return done(err);
|
192 |
|
193 | var appDir = path.join(self.program.baseDir, self.mockAnswers.appName);
|
194 |
|
195 |
|
196 | ['index.html', 'js/app.js'].forEach(function(file) {
|
197 | var filePath = path.join(appDir, 'app', file);
|
198 |
|
199 | debug('check if %s exists', filePath);
|
200 | assert.isTrue(fs.existsSync(filePath));
|
201 | });
|
202 |
|
203 | done();
|
204 | });
|
205 | });
|
206 | });
|
207 |
|
208 | it('returns error when app directory already exists', function(done) {
|
209 | _.extend(this.mockAnswers, {
|
210 | appName: shortid.generate(),
|
211 | startingMode: 'scratch',
|
212 | templateUrl: 'blank'
|
213 | });
|
214 |
|
215 | fs.mkdirSync(path.join(self.program.baseDir, this.mockAnswers.appName));
|
216 |
|
217 | createApp(this.program, function(err) {
|
218 | assert.isNotNull(err);
|
219 | assert.ok(/already exists/.test(err));
|
220 | done();
|
221 | });
|
222 | });
|
223 |
|
224 | it('creates index.html when no template chosen', function(done) {
|
225 | _.extend(this.mockAnswers, {
|
226 | startingMode: 'scratch',
|
227 | templateUrl: 'blank'
|
228 | });
|
229 |
|
230 | createApp(this.program, function(err, createdApp) {
|
231 | if (err) return done(err);
|
232 |
|
233 | var appDir = path.join(self.program.baseDir, self.mockAnswers.appName);
|
234 |
|
235 | ['index.html', 'package.json'].forEach(function(file) {
|
236 | var filePath = path.join(appDir, file);
|
237 | assert.isTrue(fs.existsSync(filePath));
|
238 | });
|
239 |
|
240 |
|
241 | var packageJson = JSON.parse(fs.readFileSync(path.join(appDir, 'package.json')));
|
242 | assert.isObject(packageJson['_virtualApp']);
|
243 | assert.equal(packageJson['_virtualApp'].appId, createdApp.appId);
|
244 |
|
245 | done();
|
246 | });
|
247 | });
|
248 |
|
249 | it('continues to ask for appName until one available', function(done) {
|
250 | request.head.restore();
|
251 |
|
252 | var headStub = sinon.stub(request, 'head');
|
253 | headStub.onCall(0).yields(null, {statusCode: 200});
|
254 | headStub.onCall(1).yields(null, {statusCode: 200});
|
255 |
|
256 | headStub.onCall(2).yields(null, {statusCode: 404});
|
257 |
|
258 | createApp(this.program, function(err) {
|
259 | if (err) return done(err);
|
260 |
|
261 |
|
262 | assert.equal(self.mockInquirer.askedCount('appName'), 3);
|
263 |
|
264 | done();
|
265 | });
|
266 | });
|
267 | });
|