Skip to content

Commit 737960b

Browse files
committed
test: add backbone for acceptance testing
- fix: publisher may throw an error before promise - fix: impossible to override node_modules inclusion patterns - fix: publisher promise doesn't respect all async work - fix: publisher may deploy empty model
1 parent 91b516a commit 737960b

5 files changed

Lines changed: 247 additions & 22 deletions

File tree

lib/server-code/model.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ class ServerCodeModel {
9696
return ServiceDescriptor.describe(name, this);
9797
}
9898

99+
isEmpty() {
100+
return this.handlers.values().length === 0 && this.services.values().length === 0;
101+
}
102+
99103
static computeHandlerKey(eventId, target) {
100104
const isTimer = events.get(eventId).provider === events.providers.TIMER;
101105

lib/server-code/publisher.js

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const buildModel = require('./model').build,
3+
const ServerCodeModel = require('./model'),
44
logger = require('../util/logger'),
55
path = require('path'),
66
fs = require('fs'),
@@ -11,35 +11,76 @@ const buildModel = require('./model').build,
1111
const APP_SIZE_LIMIT_REACHED = 'You have reached your limit';
1212

1313
exports.publish = function(opts) {
14-
const model = buildModel(process.cwd(), opts.app.files);
15-
const zipped = generateZip(model, opts.app.files.concat(dependencyPatterns()));
1614
const apiServer = new ApiServerService(opts.app, opts.backendless.apiServer);
17-
18-
let registered = false;
19-
20-
if (opts.keepZip) {
21-
fs.writeFile('deploy.zip', zipped);
22-
logger.info(`Deployment archive is saved to ${path.resolve('deploy.zip')}`);
15+
16+
let registered;
17+
let zipped;
18+
let model;
19+
20+
function buildModel() {
21+
model = ServerCodeModel.build(process.cwd(), file.expand(opts.app.files, { nodir: true }));
22+
23+
if (model.isEmpty()) {
24+
throw new Error('Nothing to publish');
25+
}
2326
}
2427

25-
Promise.resolve()
26-
.then(() => apiServer.registerRunner()) // http://bugs.backendless.com/browse/BKNDLSS-11655
27-
.then(() => registered = true)
28-
.then(() => apiServer.registerModel(model))
29-
.then(() => apiServer.publish(zipped))
30-
.catch(err => {
31-
const message = String(err.message || err);
28+
function zip() {
29+
zipped = generateZip(model, dependencyPatterns().concat(opts.app.files), opts.keepZip);
30+
}
31+
32+
function publishHandlers() {
33+
if (model.handlers.values().length) {
34+
return apiServer.registerModel(model).then(() => {
35+
return apiServer.publish(zipped);
36+
});
37+
}
38+
}
39+
40+
function publishServices() {
41+
const services = model.services.values();
42+
43+
if (services.length) {
44+
// return apiServer.unregisterDebugServices()
45+
// .then(() => Promise.all(services.map((s) => {
46+
// return apiServer.publishService(s.name, model.describeService(s.name));
47+
// })));
48+
}
49+
}
50+
51+
function registerRunner() {
52+
return apiServer.registerRunner() // http://bugs.backendless.com/browse/BKNDLSS-11655
53+
.then(() => registered = true);
54+
}
55+
56+
function finalize() {
57+
if (registered) {
58+
registered = false;
59+
60+
return apiServer.unregisterRunner();
61+
}
62+
}
3263

33-
logger.error(message);
64+
return Promise.resolve()
65+
.then(buildModel)
66+
.then(zip)
67+
.then(registerRunner)
68+
.then(publishHandlers)
69+
.then(publishServices)
70+
.then(finalize)
71+
.catch(err => {
72+
finalize();
3473

74+
const message = String(err.message || err);
3575
if (message.includes(APP_SIZE_LIMIT_REACHED)) {
3676
logger.info('You can decrease an application deployment zip size by adding an exclusion filters to your {app.files} config parameter. ');
3777
}
38-
})
39-
.then(() => registered && apiServer.unregisterRunner());
78+
79+
throw err;
80+
});
4081
};
4182

42-
function generateZip(model, patterns) {
83+
function generateZip(model, patterns, keep) {
4384
logger.info('Preparing app zip file for deployment..');
4485
logger.debug('File patterns to be included:');
4586

@@ -62,7 +103,14 @@ function generateZip(model, patterns) {
62103

63104
logger.info(`${files} files added into deployment archive`);
64105

65-
return zip.generate({ type: 'nodebuffer' });
106+
const result = zip.generate({ type: 'nodebuffer' });
107+
108+
if (keep) {
109+
fs.writeFile('deploy.zip', result);
110+
logger.info(`Deployment archive is saved to ${path.resolve('deploy.zip')}`);
111+
}
112+
113+
return result;
66114
}
67115

68116
function dependencyPatterns() {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"devDependencies": {
4545
"eslint": "^1.10.3",
4646
"mocha": "^2.4.5",
47-
"should": "^8.2.1"
47+
"should": "^8.2.1",
48+
"supertest": "^1.2.0"
4849
}
4950
}

test/acceptance/cloud.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
'use strict';
2+
3+
const serverCode = require('./server-code'),
4+
events = require('../../lib/server-code/events'),
5+
should = require('should'),
6+
PERSISTENCE = events.providers.PERSISTENCE;
7+
8+
require('mocha');
9+
10+
const app = {
11+
server : 'http://apitest.backendless.com',
12+
id : '321AEB7F-3789-7AF8-FFA5-425A7F482200',
13+
blKey : 'B742A4A3-7678-3F21-FF53-B8047CED4200',
14+
restKey: '5E99B304-EB00-9ABA-FF19-180F6B370800',
15+
version: 'v1'
16+
};
17+
18+
function request(method, path) {
19+
return require('supertest')(app.server + '/' + app.version)[method](path)
20+
.set('application-id', app.id)
21+
.set('secret-key', app.restKey);
22+
}
23+
24+
describe('In CLOUD', function() {
25+
describe('[before] event handler', function() {
26+
this.timeout(10000);
27+
28+
it('should be able to modify request', function(done) {
29+
function handler(req) {
30+
req.item.name += ' Bar';
31+
}
32+
33+
serverCode(app)
34+
.addHandler(PERSISTENCE.events.beforeCreate, handler)
35+
.deploy()
36+
.then(() => {
37+
request('post', '/data/Person')
38+
.send({ name: 'Foo' })
39+
.expect(200, /"name":"Foo Bar"/, done);
40+
})
41+
.catch(done);
42+
});
43+
44+
it('should be able to replace request', function(done) {
45+
function handler(req) {
46+
req.item = { Foo: 'Bar' };
47+
}
48+
49+
serverCode(app)
50+
.addHandler(PERSISTENCE.events.beforeCreate, handler)
51+
.deploy()
52+
.then(() => {
53+
request('post', '/data/Person')
54+
.send({ name: 'Foo' })
55+
.expect(200, /"Foo":"Bar"/, done);
56+
})
57+
.catch(done);
58+
});
59+
60+
it('should be able to prevent default behavior by throwing simple Error', function(done) {
61+
function handler() {
62+
throw new Error('You shall not pass');
63+
}
64+
65+
serverCode(app)
66+
.addHandler(PERSISTENCE.events.beforeCreate, handler)
67+
.deploy()
68+
.then(() => {
69+
request('post', '/data/Person')
70+
.send({ name: 'Foo' })
71+
.expect(400, { code: 0, message: 'You shall not pass' }, done);
72+
})
73+
.catch(done);
74+
});
75+
76+
it('should be able to prevent default behavior by throwing custom Error', function(done) {
77+
function handler() {
78+
throw new Backendless.ServerCode.Error(1000, 'You shall not pass');
79+
}
80+
81+
serverCode(app)
82+
.addHandler(PERSISTENCE.events.beforeCreate, handler)
83+
.deploy()
84+
.then(() => {
85+
request('post', '/data/Person')
86+
.send({ name: 'Foo' })
87+
.expect(400, { code: 1000, message: 'You shall not pass' }, done);
88+
})
89+
.catch(done);
90+
});
91+
});
92+
93+
describe('[after] event handler', function() {
94+
95+
});
96+
});

test/acceptance/server-code.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
'use strict';
2+
3+
const CodeRunner = require('../../lib'),
4+
fs = require('fs');
5+
6+
const DIR = 'acceptance';
7+
8+
function prepareDir() {
9+
if (fs.existsSync(DIR)) {
10+
fs.readdirSync(DIR).forEach(function(file) {
11+
fs.unlinkSync(`${DIR}/${file}`);
12+
});
13+
14+
fs.rmdirSync(DIR);
15+
}
16+
17+
fs.mkdirSync(DIR);
18+
}
19+
20+
function providerApi(provider) {
21+
return provider.id.substr(0, 1).toUpperCase() + provider.id.substring(1);
22+
}
23+
24+
let nextId = 0;
25+
26+
class ServerCode {
27+
constructor(app) {
28+
this.id = nextId++;
29+
this.app = app;
30+
this.items = [];
31+
}
32+
33+
addHandler(event, handler, context) {
34+
const p = event.provider;
35+
const ctx = p.targeted ? `'${context || '*'}'` : null;
36+
const handlerBody = `'use strict';` +
37+
`Backendless.ServerCode.${providerApi(p)}.${event.name}(${ctx ? ctx + ', ' : ''}${handler.toString()});`;
38+
39+
this.items.push(handlerBody);
40+
return this;
41+
}
42+
43+
addTimer(opts) {
44+
//TODO: implement me
45+
return this;
46+
}
47+
48+
addService(service) {
49+
//TODO: implement me
50+
return this;
51+
}
52+
53+
deploy() {
54+
prepareDir();
55+
56+
this.items.forEach((item, i) => {
57+
fs.writeFileSync(`${DIR}/bl-${nextId}-${i}.js`, item);
58+
});
59+
60+
return CodeRunner.deploy({
61+
backendless: {
62+
apiServer: this.app.server
63+
},
64+
app : {
65+
id : this.app.id,
66+
secretKey: this.app.blKey,
67+
version : this.app.version,
68+
files : ['!node_modules/**', `${DIR}/**`]
69+
}
70+
});
71+
}
72+
}
73+
74+
module.exports = function(app) {
75+
return new ServerCode(app);
76+
};

0 commit comments

Comments
 (0)