diff --git a/src/apps.js b/src/apps.js index a67529f29..448ebd227 100644 --- a/src/apps.js +++ b/src/apps.js @@ -347,15 +347,31 @@ function purchase(appStoreId, callback) { }); } +function downloadManifest(appStoreId, manifest, callback) { + if (!appStoreId) return callback(null, '', manifest); + + var parts = appStoreId.split('@'); + + var url = config.apiServerOrigin() + '/api/v1/apps/' + parts[0] + (parts[1] ? '/versions/' + parts[1] : ''); + + debug('downloading manifest from %s', url); + + superagent.get(url).end(function (error, result) { + if (error && !error.response) return callback(new AppsError(AppsError.EXTERNAL_ERROR, 'Network error downloading manifest:' + error.message)); + + if (result.statusCode !== 200) return callback(new AppsError(AppsError.EXTERNAL_ERROR, util.format('Failed to get app info from store.', result.statusCode, result.text))); + + callback(null, parts[0], result.body.manifest); + }); +} + function install(appId, data, auditSource, callback) { assert.strictEqual(typeof appId, 'string'); assert(data && typeof data === 'object'); assert.strictEqual(typeof auditSource, 'object'); assert.strictEqual(typeof callback, 'function'); - var appStoreId = data.appStoreId || '', - manifest = data.manifest, - location = data.location.toLowerCase(), + var location = data.location.toLowerCase(), portBindings = data.portBindings || null, accessRestriction = data.accessRestriction || null, icon = data.icon || null, @@ -364,75 +380,70 @@ function install(appId, data, auditSource, callback) { memoryLimit = data.memoryLimit || 0, altDomain = data.altDomain || null; - assert.strictEqual(typeof appStoreId, 'string'); - assert(manifest && typeof manifest === 'object'); - assert.strictEqual(typeof location, 'string'); - assert.strictEqual(typeof portBindings, 'object'); - assert.strictEqual(typeof accessRestriction, 'object'); - assert(icon === null || typeof icon === 'string'); - assert(cert === null || typeof cert === 'string'); - assert(key === null || typeof key === 'string'); - assert.strictEqual(typeof memoryLimit, 'number'); - assert(altDomain === null || typeof altDomain === 'string'); + assert(data.appStoreId || data.manifest); // atleast one of them is required - var error = manifestFormat.parse(manifest); - if (error) return callback(new AppsError(AppsError.BAD_FIELD, 'Manifest error: ' + error.message)); - - error = checkManifestConstraints(manifest); - if (error) return callback(error); - - error = validateHostname(location, config.fqdn()); - if (error) return callback(error); - - error = validatePortBindings(portBindings, manifest.tcpPorts); - if (error) return callback(error); - - error = validateAccessRestriction(accessRestriction); - if (error) return callback(error); - - error = validateMemoryLimit(manifest, memoryLimit); - if (error) return callback(error); - - // memoryLimit might come in as 0 if not specified - memoryLimit = memoryLimit || manifest.memoryLimit || constants.DEFAULT_MEMORY_LIMIT; - - if (altDomain !== null && !validator.isFQDN(altDomain)) return callback(new AppsError(AppsError.BAD_FIELD, 'Invalid alt domain')); - - // singleUser mode requires accessRestriction to contain exactly one user - if (manifest.singleUser && accessRestriction === null) return callback(new AppsError(AppsError.USER_REQUIRED)); - if (manifest.singleUser && accessRestriction.users.length !== 1) return callback(new AppsError(AppsError.USER_REQUIRED)); - - if (icon) { - if (!validator.isBase64(icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64')); - - if (!safe.fs.writeFileSync(path.join(paths.APPICONS_DIR, appId + '.png'), new Buffer(icon, 'base64'))) { - return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving icon:' + safe.error.message)); - } - } - - error = certificates.validateCertificate(cert, key, config.appFqdn(location)); - if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); - - debug('Will install app with id : ' + appId); - - purchase(appStoreId, function (error) { + downloadManifest(data.appStoreId, data.manifest, function (error, appStoreId, manifest) { if (error) return callback(error); - appdb.add(appId, appStoreId, manifest, location, portBindings, accessRestriction, memoryLimit, altDomain, function (error) { - if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); - if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + error = manifestFormat.parse(manifest); + if (error) return callback(new AppsError(AppsError.BAD_FIELD, 'Manifest error: ' + error.message)); - // save cert to data/box/certs - if (cert && key) { - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn(location) + '.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); - if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn(location) + '.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); + error = checkManifestConstraints(manifest); + if (error) return callback(error); + + error = validateHostname(location, config.fqdn()); + if (error) return callback(error); + + error = validatePortBindings(portBindings, manifest.tcpPorts); + if (error) return callback(error); + + error = validateAccessRestriction(accessRestriction); + if (error) return callback(error); + + error = validateMemoryLimit(manifest, memoryLimit); + if (error) return callback(error); + + // memoryLimit might come in as 0 if not specified + memoryLimit = memoryLimit || manifest.memoryLimit || constants.DEFAULT_MEMORY_LIMIT; + + if (altDomain !== null && !validator.isFQDN(altDomain)) return callback(new AppsError(AppsError.BAD_FIELD, 'Invalid alt domain')); + + // singleUser mode requires accessRestriction to contain exactly one user + if (manifest.singleUser && accessRestriction === null) return callback(new AppsError(AppsError.USER_REQUIRED)); + if (manifest.singleUser && accessRestriction.users.length !== 1) return callback(new AppsError(AppsError.USER_REQUIRED)); + + if (icon) { + if (!validator.isBase64(icon)) return callback(new AppsError(AppsError.BAD_FIELD, 'icon is not base64')); + + if (!safe.fs.writeFileSync(path.join(paths.APPICONS_DIR, appId + '.png'), new Buffer(icon, 'base64'))) { + return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving icon:' + safe.error.message)); } + } - taskmanager.restartAppTask(appId); + error = certificates.validateCertificate(cert, key, config.appFqdn(location)); + if (error) return callback(new AppsError(AppsError.BAD_CERTIFICATE, error.message)); - eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, location: location, manifest: manifest }); + debug('Will install app with id : ' + appId); - callback(null); + purchase(appStoreId, function (error) { + if (error) return callback(error); + + appdb.add(appId, appStoreId, manifest, location, portBindings, accessRestriction, memoryLimit, altDomain, function (error) { + if (error && error.reason === DatabaseError.ALREADY_EXISTS) return callback(getDuplicateErrorDetails(location, portBindings, error)); + if (error) return callback(new AppsError(AppsError.INTERNAL_ERROR, error)); + + // save cert to data/box/certs + if (cert && key) { + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn(location) + '.cert'), cert)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving cert: ' + safe.error.message)); + if (!safe.fs.writeFileSync(path.join(paths.APP_CERTS_DIR, config.appFqdn(location) + '.key'), key)) return callback(new AppsError(AppsError.INTERNAL_ERROR, 'Error saving key: ' + safe.error.message)); + } + + taskmanager.restartAppTask(appId); + + eventlog.add(eventlog.ACTION_APP_INSTALL, auditSource, { appId: appId, location: location, manifest: manifest }); + + callback(null); + }); }); }); } diff --git a/src/cloudron.js b/src/cloudron.js index abec2904d..0b7ebe235 100644 --- a/src/cloudron.js +++ b/src/cloudron.js @@ -655,28 +655,16 @@ function installAppBundle(callback) { } async.eachSeries(bundle, function (appInfo, iteratorCallback) { - var appstoreId = appInfo.appstoreId; - var parts = appstoreId.split('@'); + debug('autoInstall: installing %s at %s', appInfo.appstoreId, appInfo.location); - var url = config.apiServerOrigin() + '/api/v1/apps/' + parts[0] + (parts[1] ? '/versions/' + parts[1] : ''); + var data = { + appStoreId: appInfo.appstoreId, + location: appInfo.location, + portBindings: appInfo.portBindings || null, + accessRestriction: appInfo.accessRestriction || null, + }; - superagent.get(url).end(function (error, result) { - if (error && !error.response) return iteratorCallback(new Error('Network error: ' + error.message)); - - if (result.statusCode !== 200) return iteratorCallback(util.format('Failed to get app info from store.', result.statusCode, result.text)); - - debug('autoInstall: installing %s at %s', appstoreId, appInfo.location); - - var data = { - appStoreId: appstoreId, - manifest: result.body.manifest, - location: appInfo.location, - portBindings: appInfo.portBindings || null, - accessRestriction: appInfo.accessRestriction || null, - }; - - apps.install(uuid.v4(), data, { userId: null, username: 'autoinstaller' }, iteratorCallback); - }); + apps.install(uuid.v4(), data, { userId: null, username: 'autoinstaller' }, iteratorCallback); }, function (error) { if (error) debug('autoInstallApps: ', error); diff --git a/src/routes/apps.js b/src/routes/apps.js index c1344763c..a0f207df1 100644 --- a/src/routes/apps.js +++ b/src/routes/apps.js @@ -95,9 +95,12 @@ function installApp(req, res, next) { var data = req.body; + // atleast one + if ('manifest' in data && typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest is required')); + if ('appStoreId' in data && typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId is required')); + if (!data.manifest && !data.appStoreId) return next(new HttpError(400, 'appStoreId or manifest is required')); + // required - if (!data.manifest || typeof data.manifest !== 'object') return next(new HttpError(400, 'manifest is required')); - if (typeof data.appStoreId !== 'string') return next(new HttpError(400, 'appStoreId is required')); if (typeof data.location !== 'string') return next(new HttpError(400, 'location is required')); if (typeof data.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction is required')); @@ -125,6 +128,7 @@ function installApp(req, res, next) { if (error && error.reason === AppsError.NOT_FOUND) return next(new HttpError(404, 'No such app')); if (error && error.reason === AppsError.BAD_CERTIFICATE) return next(new HttpError(400, error.message)); if (error && error.reason === AppsError.USER_REQUIRED) return next(new HttpError(400, 'accessRestriction must specify one user')); + if (error && error.reason === AppsError.EXTERNAL_ERROR) return next(new HttpError(503, error.message)); if (error) return next(new HttpError(500, error)); next(new HttpSuccess(202, { id: appId } )); diff --git a/src/routes/test/apps-test.js b/src/routes/test/apps-test.js index 97af4e766..3f8c5472e 100644 --- a/src/routes/test/apps-test.js +++ b/src/routes/test/apps-test.js @@ -221,26 +221,15 @@ describe('Apps', function () { it('app install fails - missing manifest', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, password: PASSWORD }) + .send({ password: PASSWORD }) .end(function (err, res) { expect(res.statusCode).to.equal(400); - expect(res.body.message).to.eql('manifest is required'); + expect(res.body.message).to.eql('appStoreId or manifest is required'); done(); }); }); - it('app install fails - missing appId', function (done) { - superagent.post(SERVER_URL + '/api/v1/apps/install') - .query({ access_token: token }) - .send({ manifest: APP_MANIFEST, password: PASSWORD }) - .end(function (err, res) { - expect(res.statusCode).to.equal(400); - expect(res.body.message).to.eql('appStoreId is required'); - done(); - }); - }); - - it('app install fails - invalid json', function (done) { + it('app install fails - invalid json', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) .send('garbage') @@ -253,7 +242,7 @@ describe('Apps', function () { it('app install fails - invalid location', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: '!awesome', accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: '!awesome', accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('Hostname can only contain alphanumerics and hyphen'); @@ -264,7 +253,7 @@ describe('Apps', function () { it('app install fails - invalid location type', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: 42, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: 42, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('location is required'); @@ -275,7 +264,7 @@ describe('Apps', function () { it('app install fails - reserved admin location', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.ADMIN_LOCATION, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: constants.ADMIN_LOCATION, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql(constants.ADMIN_LOCATION + ' is reserved'); @@ -286,7 +275,7 @@ describe('Apps', function () { it('app install fails - reserved api location', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: constants.API_LOCATION, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: constants.API_LOCATION, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql(constants.API_LOCATION + ' is reserved'); @@ -297,7 +286,7 @@ describe('Apps', function () { it('app install fails - portBindings must be object', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: 23, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: 23, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('portBindings must be an object'); @@ -308,7 +297,7 @@ describe('Apps', function () { it('app install fails - accessRestriction is required', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {} }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {} }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('accessRestriction is required'); @@ -319,7 +308,7 @@ describe('Apps', function () { it('app install fails - accessRestriction type is wrong', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: '' }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: '' }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('accessRestriction is required'); @@ -330,7 +319,7 @@ describe('Apps', function () { it('app install fails - accessRestriction no users not allowed', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null }) + .send({ manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('accessRestriction must specify one user'); @@ -341,7 +330,7 @@ describe('Apps', function () { it('app install fails - accessRestriction too many users not allowed', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: { users: [ 'one', 'two' ] } }) + .send({ manifest: APP_MANIFEST_1, password: PASSWORD, location: APP_LOCATION, portBindings: {}, accessRestriction: { users: [ 'one', 'two' ] } }) .end(function (err, res) { expect(res.statusCode).to.equal(400); expect(res.body.message).to.eql('accessRestriction must specify one user'); @@ -352,50 +341,64 @@ describe('Apps', function () { it('app install fails for non admin', function (done) { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token_1 }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(403); done(); }); }); - it('app install fails due to purchase failure', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(402, {}); + it('app install fails because manifest download fails', function (done) { + var fake = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(404, {}); superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) + .send({ appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: { users: [ 'someuser' ], groups: [] } }) + .end(function (err, res) { + expect(res.statusCode).to.equal(503); + expect(fake.isDone()).to.be.ok(); + done(); + }); + }); + + it('app install fails due to purchase failure', function (done) { + var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST }); + var fake2 = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(402, {}); + + superagent.post(SERVER_URL + '/api/v1/apps/install') + .query({ access_token: token }) + .send({ appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(402); - expect(fake.isDone()).to.be.ok(); + expect(fake1.isDone()).to.be.ok(); + expect(fake2.isDone()).to.be.ok(); done(); }); }); it('app install succeeds with purchase', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); + var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST }); + var fake2 = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: { users: [ 'someuser' ], groups: [] } }) + .send({ appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: { users: [ 'someuser' ], groups: [] } }) .end(function (err, res) { expect(res.statusCode).to.equal(202); expect(res.body.id).to.be.a('string'); APP_ID = res.body.id; - expect(fake.isDone()).to.be.ok(); + expect(fake1.isDone()).to.be.ok(); + expect(fake2.isDone()).to.be.ok(); done(); }); }); it('app install fails because of conflicting location', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); - superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(409); - expect(fake.isDone()).to.be.ok(); done(); }); }); @@ -493,23 +496,23 @@ describe('Apps', function () { }); it('app install succeeds already purchased', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(200, {}); + var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST }); + var fake2 = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(200, {}); superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION_2, portBindings: null, accessRestriction: null }) + .send({ appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION_2, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(202); expect(res.body.id).to.be.a('string'); APP_ID = res.body.id; - expect(fake.isDone()).to.be.ok(); + expect(fake1.isDone()).to.be.ok(); + expect(fake2.isDone()).to.be.ok(); done(); }); }); it('app install succeeds without password but developer token', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); - settings.setDeveloperMode(true, function (error) { expect(error).to.be(null); @@ -526,11 +529,10 @@ describe('Apps', function () { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, location: APP_LOCATION+APP_LOCATION, portBindings: null, accessRestriction: null }) + .send({ manifest: APP_MANIFEST, location: APP_LOCATION+APP_LOCATION, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(202); expect(res.body.id).to.be.a('string'); - expect(fake.isDone()).to.be.ok(); APP_ID = res.body.id; done(); }); @@ -597,7 +599,8 @@ describe('Apps', function () { var appResult = null /* the json response */, appEntry = null /* entry from database */; it('can install test app', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); + var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST }); + var fake2 = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(200, {}); var count = 0; function checkInstallStatus() { @@ -614,10 +617,11 @@ describe('Apps', function () { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) + .send({ appId: APP_ID, appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION, portBindings: null, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(202); - expect(fake.isDone()).to.be.ok(); + expect(fake1.isDone()).to.be.ok(); + expect(fake2.isDone()).to.be.ok(); expect(res.body.id).to.be.a('string'); expect(res.body.id).to.be.eql(APP_ID); checkInstallStatus(); @@ -1038,7 +1042,8 @@ describe('Apps', function () { var appResult = null, appEntry = null; it('can install test app', function (done) { - var fake = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); + var fake1 = nock(config.apiServerOrigin()).get('/api/v1/apps/test').reply(200, { manifest: APP_MANIFEST }); + var fake2 = nock(config.apiServerOrigin()).post('/api/v1/apps/test/purchase?token=APPSTORE_TOKEN').reply(201, {}); var count = 0; function checkInstallStatus() { @@ -1055,10 +1060,11 @@ describe('Apps', function () { superagent.post(SERVER_URL + '/api/v1/apps/install') .query({ access_token: token }) - .send({ appId: APP_ID, appStoreId: APP_STORE_ID, manifest: APP_MANIFEST, password: PASSWORD, location: APP_LOCATION, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null }) + .send({ appId: APP_ID, appStoreId: APP_STORE_ID, password: PASSWORD, location: APP_LOCATION, portBindings: { ECHO_SERVER_PORT: 7171 }, accessRestriction: null }) .end(function (err, res) { expect(res.statusCode).to.equal(202); - expect(fake.isDone()).to.be.ok(); + expect(fake1.isDone()).to.be.ok(); + expect(fake2.isDone()).to.be.ok(); expect(res.body.id).to.equal(APP_ID); checkInstallStatus(); });