1
0
Fork 0

Bugfix Foxx API tests (#4446)

This commit is contained in:
Mark 2018-02-05 11:31:27 +01:00 committed by Michael Hackstein
parent 58cfd340cc
commit 599da158b5
27 changed files with 806 additions and 72 deletions

View File

@ -17,4 +17,7 @@ Any omitted options will be reset to their default values or marked as unconfigu
@RESTQUERYPARAM{mount,string,required}
Mount path of the installed service.
@RESTRETURNCODE{200}
Returned if the request was sucessful.
@endDocuBlock

View File

@ -17,4 +17,7 @@ Any omitted options will be ignored.
@RESTQUERYPARAM{mount,string,required}
Mount path of the installed service.
@RESTRETURNCODE{200}
Returned if the request was sucessful.
@endDocuBlock

View File

@ -17,4 +17,7 @@ Any omitted dependencies will be disabled.
@RESTQUERYPARAM{mount,string,required}
Mount path of the installed service.
@RESTRETURNCODE{200}
Returned if the request was sucessful.
@endDocuBlock

View File

@ -17,4 +17,7 @@ Any omitted dependencies will be ignored.
@RESTQUERYPARAM{mount,string,required}
Mount path of the installed service.
@RESTRETURNCODE{200}
Returned if the request was sucessful.
@endDocuBlock

View File

@ -28,7 +28,8 @@ in the field `main` of the service manifest.
If *source* is a URL, the URL must be reachable from the server.
If *source* is a file system path, the path will be resolved on the server.
In either case the path or URL is expected to resolve to a zip bundle.
In either case the path or URL is expected to resolve to a zip bundle,
JavaScript file or (in case of a file system path) directory.
Note that when using file system paths in a cluster with multiple coordinators
the file system path must resolve to equivalent files on every coordinator.

View File

@ -18,6 +18,11 @@ Additionally the object may contain the following attributes if they have been s
- *name*: a string identifying the service type
- *version*: a semver-compatible version string
@RESTQUERYPARAMETERS
@RESTQUERYPARAM{excludeSystem,boolean,optional}
Whether or not system services should be excluded from the result.
@RESTRETURNCODES
@RESTRETURNCODE{200}

View File

@ -33,7 +33,8 @@ in the field `main` of the service manifest.
If *source* is a URL, the URL must be reachable from the server.
If *source* is a file system path, the path will be resolved on the server.
In either case the path or URL is expected to resolve to a zip bundle.
In either case the path or URL is expected to resolve to a zip bundle,
JavaScript file or (in case of a file system path) directory.
Note that when using file system paths in a cluster with multiple coordinators
the file system path must resolve to equivalent files on every coordinator.

View File

@ -33,7 +33,8 @@ in the field `main` of the service manifest.
If *source* is a URL, the URL must be reachable from the server.
If *source* is a file system path, the path will be resolved on the server.
In either case the path or URL is expected to resolve to a zip bundle.
In either case the path or URL is expected to resolve to a zip bundle,
JavaScript file or (in case of a file system path) directory.
Note that when using file system paths in a cluster with multiple coordinators
the file system path must resolve to equivalent files on every coordinator.

View File

@ -130,6 +130,7 @@ router.post(prepareServiceRequestBody, (req, res) => {
));
const service = FoxxManager.lookupService(mount);
res.json(serviceToJson(service));
res.status(201);
})
.body(schemas.service, ['application/javascript', 'application/zip', 'multipart/form-data', 'application/json'])
.queryParam('mount', schemas.mount)
@ -284,7 +285,7 @@ depsRouter.get((req, res) => {
depsRouter.patch((req, res) => {
const warnings = FoxxManager.setDependencies(req.service.mount, {
dependencies: req.body,
replace: true
replace: false
});
const values = req.service.getDependencies(req.queryParams.minimal);
if (req.queryParams.minimal) {

View File

@ -4,9 +4,11 @@
const expect = require('chai').expect;
const FoxxManager = require('@arangodb/foxx/manager');
const request = require('@arangodb/request');
const util = require('@arangodb/util');
const fs = require('fs');
const internal = require('internal');
const basePath = fs.makeAbsolute(fs.join(internal.startupPath, 'common', 'test-data', 'apps', 'headers'));
const path = require('path');
const basePath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'headers');
const arangodb = require('@arangodb');
const db = arangodb.db;
const aql = arangodb.aql;
@ -138,3 +140,618 @@ describe('FoxxApi commit', function () {
expect(bundles).to.equal(1);
});
});
describe('Foxx service', () => {
const mount = '/foxx-crud-test';
const basePath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'minimal-working-service');
const itzPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'itzpapalotl');
var utils = require('@arangodb/foxx/manager-utils');
const servicePath = utils.zipDirectory(basePath);
const serviceServiceMount = '/foxx-crud-test-download';
const serviceServicePath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'service-service', 'index.js');
beforeEach(() => {
FoxxManager.install(serviceServicePath, serviceServiceMount);
});
afterEach(() => {
try {
FoxxManager.uninstall(serviceServiceMount, {force: true});
} catch (e) {}
});
afterEach(() => {
try {
FoxxManager.uninstall(mount, {force: true});
} catch (e) {}
});
const cases = [
{
name: 'localJsFile',
request: {
qs: {
mount
},
body: {
source: path.resolve(basePath, 'index.js')
},
json: true
}
},
{
name: 'localZipFile',
request: {
qs: {
mount
},
body: {
source: servicePath
},
json: true
}
},
{
name: 'localDir',
request: {
qs: {
mount
},
body: {
source: basePath
},
json: true
}
},
{
name: 'jsBuffer',
request: {
qs: {
mount
},
body: fs.readFileSync(path.resolve(basePath, 'index.js')),
contentType: 'application/javascript'
}
},
{
name: 'zipBuffer',
request: {
qs: {
mount
},
body: fs.readFileSync(servicePath),
contentType: 'application/zip'
}
},
{
name: 'remoteJsFile',
request: {
qs: {
mount
},
body: {
source: `${origin}/_db/${db._name()}${serviceServiceMount}/js`
},
json: true
}
},
{
name: 'remoteZipFile',
request: {
qs: {
mount
},
body: {
source: `${origin}/_db/${db._name()}${serviceServiceMount}/zip`
},
json: true
}
}
];
for (const c of cases) {
it(`installed via ${c.name} should be available`, () => {
const installResp = request.post('/_api/foxx', c.request);
expect(installResp.status).to.equal(201);
const resp = request.get(c.request.qs.mount);
expect(resp.json).to.eql({hello: 'world'});
});
it(`replaced via ${c.name} should be available`, () => {
FoxxManager.install(itzPath, c.request.qs.mount);
const replaceResp = request.put('/_api/foxx/service', c.request);
expect(replaceResp.status).to.equal(200);
const resp = request.get(c.request.qs.mount);
expect(resp.json).to.eql({hello: 'world'});
});
it(`upgrade via ${c.name} should be available`, () => {
FoxxManager.install(itzPath, c.request.qs.mount);
const upgradeResp = request.patch('/_api/foxx/service', c.request);
expect(upgradeResp.status).to.equal(200);
const resp = request.get(c.request.qs.mount);
expect(resp.json).to.eql({hello: 'world'});
});
}
it('uninstalled should not be available', () => {
FoxxManager.install(basePath, mount);
const delResp = request.delete('/_api/foxx/service', {qs: {mount}});
expect(delResp.status).to.equal(204);
expect(delResp.body).to.equal('');
const resp = request.get(mount);
expect(resp.status).to.equal(404);
});
const confPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'with-configuration');
it('empty configuration should be available', () => {
FoxxManager.install(basePath, mount);
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.eql({});
});
it('configuration should be available', () => {
FoxxManager.install(confPath, mount);
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.not.have.property('current');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('configuration should be available after update', () => {
FoxxManager.install(confPath, mount);
const updateResp = request.patch('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test1: 'test'
},
json: true
});
expect(updateResp.status).to.equal(200);
expect(updateResp.json).to.have.property('values');
expect(updateResp.json.values).to.have.property('test1', 'test');
expect(updateResp.json.values).to.not.have.property('test2');
expect(updateResp.json).to.not.have.property('warnings');
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', 'test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('configuration should be available after replace', () => {
FoxxManager.install(confPath, mount);
const replaceResp = request.put('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test1: 'test'
},
json: true
});
expect(replaceResp.status).to.equal(200);
expect(replaceResp.json).to.have.property('values');
expect(replaceResp.json.values).to.have.property('test1', 'test');
expect(replaceResp.json.values).to.not.have.property('test2');
expect(replaceResp.json).to.have.property('warnings');
expect(replaceResp.json.warnings).to.have.property('test2', 'is required');
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', 'test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('configuration should be merged after update', () => {
FoxxManager.install(confPath, mount);
const replaceResp = request.put('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test2: 'test2'
},
json: true
});
expect(replaceResp.status).to.equal(200);
const updateResp = request.patch('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test1: 'test1'
},
json: true
});
expect(updateResp.status).to.equal(200);
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', 'test1');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.have.property('current', 'test2');
});
it('configuration should be overwritten after replace', () => {
FoxxManager.install(confPath, mount);
const updateResp = request.patch('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test2: 'test2'
},
json: true
});
expect(updateResp.status).to.equal(200);
const replaceResp = request.put('/_api/foxx/configuration', {
qs: {
mount
},
body: {
test1: 'test'
},
json: true
});
expect(replaceResp.status).to.equal(200);
const resp = request.get('/_api/foxx/configuration', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', 'test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
const depPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'with-dependencies');
it('empty configuration should be available', () => {
FoxxManager.install(basePath, mount);
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.eql({});
});
it('dependencies should be available', () => {
FoxxManager.install(depPath, mount);
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.not.have.property('current');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('dependencies should be available after update', () => {
FoxxManager.install(depPath, mount);
const updateResp = request.patch('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test1: '/test'
},
json: true
});
expect(updateResp.status).to.equal(200);
expect(updateResp.json).to.have.property('values');
expect(updateResp.json.values).to.have.property('test1', '/test');
expect(updateResp.json.values).not.to.have.property('test2');
expect(updateResp.json).to.not.have.property('warnings');
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', '/test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('dependencies should be available after replace', () => {
FoxxManager.install(depPath, mount);
const replaceResp = request.put('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test1: '/test'
},
json: true
});
expect(replaceResp.status).to.equal(200);
expect(replaceResp.json).to.have.property('values');
expect(replaceResp.json.values).to.have.property('test1', '/test');
expect(replaceResp.json.values).to.not.have.property('test2');
expect(replaceResp.json).to.have.property('warnings');
expect(replaceResp.json.warnings).to.have.property('test2', 'is required');
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', '/test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('dependencies should be merged after update', () => {
FoxxManager.install(depPath, mount);
const replaceResp = request.put('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test2: '/test2'
},
json: true
});
expect(replaceResp.status).to.equal(200);
expect(replaceResp.json).to.have.property('values');
expect(replaceResp.json.values).to.have.property('test2', '/test2');
expect(replaceResp.json.values).to.not.have.property('test1');
expect(replaceResp.json).to.have.property('warnings');
expect(replaceResp.json.warnings).to.have.property('test1', 'is required');
const updateResp = request.patch('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test1: '/test1'
},
json: true
});
expect(updateResp.status).to.equal(200);
expect(updateResp.json).to.have.property('values');
expect(updateResp.json.values).to.have.property('test1', '/test1');
expect(updateResp.json.values).to.have.property('test2', '/test2');
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', '/test1');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.have.property('current', '/test2');
});
it('dependencies should be overwritten after replace', () => {
FoxxManager.install(depPath, mount);
const updateResp = request.patch('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test2: '/test2'
},
json: true
});
expect(updateResp.status).to.equal(200);
expect(updateResp.json).to.have.property('values');
expect(updateResp.json).to.not.have.property('warnings');
expect(updateResp.json.values).to.have.property('test2', '/test2');
expect(updateResp.json.values).to.not.have.property('test1');
const replaceResp = request.put('/_api/foxx/dependencies', {
qs: {
mount
},
body: {
test1: '/test'
},
json: true
});
expect(replaceResp.status).to.equal(200);
expect(replaceResp.json).to.have.property('values');
expect(replaceResp.json.values).to.have.property('test1', '/test');
expect(replaceResp.json.values).to.not.have.property('test2');
expect(replaceResp.json).to.have.property('warnings');
expect(replaceResp.json.warnings).to.have.property('test2', 'is required');
const resp = request.get('/_api/foxx/dependencies', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('test1');
expect(resp.json.test1).to.have.property('current', '/test');
expect(resp.json).to.have.property('test2');
expect(resp.json.test2).to.not.have.property('current');
});
it('should be downloadable', () => {
FoxxManager.install(basePath, mount);
const resp = request.post('/_api/foxx/download', {
qs: {mount},
encoding: null
});
expect(resp.status).to.equal(200);
expect(resp.headers['content-type']).to.equal('application/zip');
expect(util.isZipBuffer(resp.body)).to.equal(true);
});
it('list should allow excluding system services', () => {
FoxxManager.install(basePath, mount);
const withSystem = request.get('/_api/foxx');
const withoutSystem = request.get('/_api/foxx', {qs: {excludeSystem: true}});
const numSystemWithSystem = withSystem.json.map(service => service.mount).filter(mount => mount.startsWith('/_')).length;
const numSystemWithoutSystem = withoutSystem.json.map(service => service.mount).filter(mount => mount.startsWith('/_')).length;
expect(numSystemWithSystem).to.above(0);
expect(numSystemWithSystem).to.equal(withSystem.json.length - withoutSystem.json.length);
expect(numSystemWithoutSystem).to.equal(0);
});
it('should be contained in service list', () => {
FoxxManager.install(basePath, mount);
const resp = request.get('/_api/foxx');
const service = resp.json.find(service => service.mount === mount);
expect(service).to.have.property('name', 'minimal-working-manifest');
expect(service).to.have.property('version', '0.0.0');
expect(service).to.have.property('provides');
expect(service.provides).to.eql({});
expect(service).to.have.property('development', false);
expect(service).to.have.property('legacy', false);
});
it('informations should be returned', () => {
FoxxManager.install(basePath, mount);
const resp = request.get('/_api/foxx/service', {qs: {mount}});
const service = resp.json;
expect(service).to.have.property('mount', mount);
expect(service).to.have.property('name', 'minimal-working-manifest');
expect(service).to.have.property('version', '0.0.0');
expect(service).to.have.property('development', false);
expect(service).to.have.property('legacy', false);
expect(service).to.have.property('manifest');
expect(service.manifest).to.be.an('object');
expect(service).to.have.property('options');
expect(service.options).to.be.an('object');
expect(service).to.have.property('checksum');
expect(service.checksum).to.be.a('string');
});
const scriptPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'minimal-working-setup-teardown');
it('list of scripts should be available', () => {
FoxxManager.install(scriptPath, mount);
const resp = request.get('/_api/foxx/scripts', {qs: {mount}});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('setup', 'Setup');
expect(resp.json).to.have.property('teardown', 'Teardown');
});
it('script should be available', () => {
FoxxManager.install(scriptPath, mount);
const col = `${mount}_setup_teardown`.replace(/\//, '').replace(/-/g, '_');
expect(db._collection(col)).to.be.an('object');
const resp = request.post('/_api/foxx/scripts/teardown', {
qs: {mount},
body: {},
json: true
});
expect(resp.status).to.equal(200);
db._flushCache();
expect(db._collection(col)).to.equal(null);
});
it('non-existing script should not be available', () => {
FoxxManager.install(scriptPath, mount);
const resp = request.post('/_api/foxx/scripts/no', {
qs: {mount},
body: {},
json: true
});
expect(resp.status).to.equal(400);
});
const echoPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'echo-script');
it('should pass argv to script and return exports', () => {
FoxxManager.install(echoPath, mount);
const argv = {hello: 'world'};
const resp = request.post('/_api/foxx/scripts/echo', {
qs: {mount},
body: argv,
json: true
});
expect(resp.json).to.eql([argv]);
});
it('should treat array script argv like any other script argv', () => {
FoxxManager.install(echoPath, mount);
const argv = ['yes', 'please'];
const resp = request.post('/_api/foxx/scripts/echo', {
qs: {mount},
body: argv,
json: true
});
expect(resp.json).to.eql([argv]);
});
it('set devmode should enable devmode', () => {
FoxxManager.install(basePath, mount);
const resp = request.get('/_api/foxx/service', {
qs: {mount},
json: true
});
expect(resp.json.development).to.equal(false);
const devResp = request.post('/_api/foxx/development', {
qs: {mount},
json: true
});
expect(devResp.json.development).to.equal(true);
const respAfter = request.get('/_api/foxx/service', {
qs: {mount},
json: true
});
expect(respAfter.json.development).to.equal(true);
});
it('clear devmode should disable devmode', () => {
FoxxManager.install(basePath, mount, {development: true});
const resp = request.get('/_api/foxx/service', {
qs: {mount},
json: true
});
expect(resp.json.development).to.equal(true);
const devResp = request.delete('/_api/foxx/development', {
qs: {mount},
json: true
});
expect(devResp.json.development).to.equal(false);
const respAfter = request.get('/_api/foxx/service', {
qs: {mount},
json: true
});
expect(respAfter.json.development).to.equal(false);
});
const routes = [
['GET', '/_api/foxx/service'],
['PATCH', '/_api/foxx/service'],
['PUT', '/_api/foxx/service'],
['DELETE', '/_api/foxx/service'],
['GET', '/_api/foxx/configuration'],
['PATCH', '/_api/foxx/configuration'],
['PUT', '/_api/foxx/configuration'],
['GET', '/_api/foxx/dependencies'],
['PATCH', '/_api/foxx/dependencies'],
['PUT', '/_api/foxx/dependencies'],
['POST', '/_api/foxx/development'],
['DELETE', '/_api/foxx/development'],
['GET', '/_api/foxx/scripts'],
['POST', '/_api/foxx/scripts/xxx'],
['POST', '/_api/foxx/tests'],
['POST', '/_api/foxx/download'],
['GET', '/_api/foxx/readme'],
['GET', '/_api/foxx/swagger']
];
for (const [method, url] of routes) {
it(`should return 400 when mount is omitted for ${method} ${url}`, () => {
const resp = request({
method,
url,
json: true
});
expect(resp.status).to.equal(400);
});
it(`should return 400 when mount is invalid for ${method} ${url}`, () => {
const resp = request({
method,
url,
qs: {mount: '/dev/null'},
json: true
});
expect(resp.status).to.equal(400);
});
}
it('tests should run', () => {
const testPath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'with-tests');
FoxxManager.install(testPath, mount);
const resp = request.post('/_api/foxx/tests', {qs: { mount }});
expect(resp.status).to.equal(200);
expect(resp.json).to.have.property('stats');
expect(resp.json).to.have.property('tests');
expect(resp.json).to.have.property('pending');
expect(resp.json).to.have.property('failures');
expect(resp.json).to.have.property('passes');
});
});

View File

@ -0,0 +1,2 @@
'use strict';
module.exports = module.context.argv;

View File

@ -0,0 +1,5 @@
{
"scripts": {
"echo": "echo.js"
}
}

View File

@ -1,5 +1,5 @@
/*jslint indent: 2, nomen: true, maxlen: 100, white: true, plusplus: true, unparam: true */
/*global applicationContext */
/*global require */
////////////////////////////////////////////////////////////////////////////////
/// @brief An example Foxx-Application for ArangoDB
@ -29,11 +29,12 @@
////////////////////////////////////////////////////////////////////////////////
(function() {
'use strict';
"use strict";
// initialize a new FoxxApplication
var FoxxApplication = require("@arangodb/foxx").Controller;
var controller = new FoxxApplication(applicationContext);
const createRouter = require('@arangodb/foxx/router');
const router = createRouter();
module.context.use(router);
// include console module so we can log something (in the server's log)
var console = require("console");
@ -42,7 +43,7 @@
// we also need this module for custom responses
var actions = require("@arangodb/actions");
// use joi for validation
// use joi for validation
var joi = require("joi");
// our app is about the following Aztec deities:
@ -79,10 +80,10 @@
// install index route (this is the default route mentioned in manifest.json)
// this route will create an HTML overview page
controller.get('/index', function (req, res) {
res.contentType = "text/html";
router.get('/index', function (req, res) {
res.set("content-type", "text/html");
var body = "<h1>" + applicationContext.name + " (" + applicationContext.version + ")</h1>";
var body = "<h1>" + module.context.service.manifest.name + " (" + module.context.service.manifest.version + ")</h1>";
body += "<h2>an example application demoing a few Foxx features</h2>";
deities.forEach(function(deity) {
@ -96,41 +97,31 @@
.summary("prints an overview page");
// install route to return a random deity name in JSON
controller.get('/random', function (req, res) {
router.get('/random', function (req, res) {
var idx = Math.round(Math.random() * (deities.length - 1));
res.json({ name: deities[idx] });
})
.summary("returns a random deity name");
// install deity-specific route for summoning
// deity name is passed as part of the URL
controller.get('/:deity/summon', function (req, res) {
var deity = req.params("deity");
router.get('/:deity/summon', function (req, res) {
var deity = req.pathParams.deity;
console.log("request to summon %s", deity);
if (deities.indexOf(deity) === -1) {
// unknown deity
throw new ArangoError();
res.throw(404, "The requested deity could not be found");
}
console.log("summoning %s", deity);
res.json({ name: deity, summoned: true });
})
.summary("summons the requested deity")
.pathParam("deity", {
type: joi.string().required()
})
.errorResponse(
Error, actions.HTTP_NOT_FOUND, "The requested deity could not be found", function(e) {
return {
error: true,
code: actions.HTTP_NOT_FOUND,
errorMessage: "The requested deity could not be found"
};
}
.pathParam("deity",
joi.string().required()
);
}());

View File

@ -1,15 +1,15 @@
{
"name": "itzpapalotl",
"version": "0.0.6",
"engines": {
"arangodb": "^2.8.0"
},
"version": "1.2.0",
"author": "jsteemann",
"description": "random aztec god service",
"description": "random Aztec deity service",
"license": "Apache License, Version 2.0",
"controllers": {
"/": "itzpapalotl.js"
"main": "itzpapalotl.js",
"engines": {
"arangodb": "^3.0"
},
"defaultDocument": "index"
}
}

View File

@ -1,7 +0,0 @@
var FoxxApplication = require("@arangodb/foxx").Controller;
var controller = new FoxxApplication(applicationContext);
controller.get('/test', function (req, res) {
'use strict';
res.json(true);
});

View File

@ -0,0 +1,7 @@
'use strict';
const router = require('@arangodb/foxx/router')();
module.context.use(router);
router.get('/test', function (req, res) {
res.json(true);
});

View File

@ -1,14 +1,9 @@
{
"name": "minimal-working-manifest",
"version": "0.0.0",
"engines": {
"arangodb": "^2.8.0"
},
"main": "index.js",
"scripts": {
"setup": "setup.js",
"teardown": "teardown.js"
},
"controllers": {
"/": "controller.js"
}
}

View File

@ -1,6 +1,8 @@
var db = require("internal").db;
var col = applicationContext.collectionName("setup_teardown");
if (!db._collection(col)) {
db._create(col);
'use strict';
const db = require('@arangodb').db;
const name = module.context.collectionName('setup_teardown');
if (!db._collection(name)) {
db._create(name);
}

View File

@ -1,3 +1,5 @@
var db = require("internal").db;
var col = applicationContext.collectionName("setup_teardown");
db._drop(col);
'use strict';
const db = require('@arangodb').db;
const name = module.context.collectionName('setup_teardown');
db._drop(name);

View File

@ -0,0 +1,19 @@
'use strict';
const path = require('path');
const internal = require('internal');
const utils = require('@arangodb/foxx/manager-utils');
const router = require('@arangodb/foxx/router')();
const basePath = path.resolve(internal.startupPath, 'common', 'test-data', 'apps', 'minimal-working-service');
const servicePath = utils.zipDirectory(basePath);
module.context.use(router);
router.get('zip', (req, res) => {
res.download(servicePath, 'service.zip');
})
.response(200, ['application/zip']);
router.get('js', (req, res) => {
res.download(path.resolve(basePath, 'index.js'), 'service.js');
})
.response(200, ['application/javascript']);

View File

@ -0,0 +1,12 @@
{
"configuration": {
"test1": {
"description": "a string",
"type": "string"
},
"test2": {
"description": "another string",
"type": "string"
}
}
}

View File

@ -0,0 +1,10 @@
{
"dependencies": {
"test1": {
"description": "a string"
},
"test2": {
"description": "another string"
}
}
}

View File

@ -0,0 +1,3 @@
{
"tests": ["test1.js", "test2.js"]
}

View File

@ -0,0 +1,20 @@
/* global describe, it */
'use strict';
const expect = require('chai').expect;
describe('boolean comparison', () => {
it('should succeed for identity', () => {
expect(true).to.equal(true);
});
it('should fail for different values', () => {
expect(true).to.equal(false);
});
});
describe('another boolean comparison', () => {
it('should succeed for identity', () => {
expect(true).to.equal(true);
});
});

View File

@ -0,0 +1,20 @@
/* global describe, it */
'use strict';
const expect = require('chai').expect;
describe('boolean comparison', () => {
it('should succeed for identity', () => {
expect(true).to.equal(true);
});
it('should fail for different values', () => {
expect(true).to.equal(false);
});
});
describe('another boolean comparison', () => {
it('should succeed for identity', () => {
expect(true).to.equal(true);
});
});

View File

@ -545,24 +545,22 @@ function _prepareService (serviceInfo, options = {}) {
fs.move(tempFile, tempBundlePath);
} else if (serviceInfo instanceof Buffer) {
// Buffer (js)
const manifest = JSON.stringify({main: 'index.js'}, null, 4);
fs.makeDirectoryRecursive(tempServicePath);
fs.writeFileSync(path.join(tempServicePath, 'index.js'), serviceInfo);
fs.writeFileSync(path.join(tempServicePath, 'manifest.json'), manifest);
utils.zipDirectory(tempServicePath, tempBundlePath);
_buildServiceBundleFromScript(tempServicePath, tempBundlePath, serviceInfo);
} else if (/^https?:/i.test(serviceInfo)) {
// Remote path
const tempFile = downloadServiceBundleFromRemote(serviceInfo);
extractServiceBundle(tempFile, tempServicePath);
fs.move(tempFile, tempBundlePath);
try {
_buildServiceFromFile(tempServicePath, tempBundlePath, tempFile);
} finally {
fs.remove(tempFile);
}
} else if (fs.exists(serviceInfo)) {
// Local path
if (fs.isDirectory(serviceInfo)) {
utils.zipDirectory(serviceInfo, tempBundlePath);
extractServiceBundle(tempBundlePath, tempServicePath);
} else {
extractServiceBundle(serviceInfo, tempServicePath);
fs.copyFile(serviceInfo, tempBundlePath);
_buildServiceFromFile(tempServicePath, tempBundlePath, serviceInfo);
}
} else {
// Foxx Store
@ -615,6 +613,24 @@ function _prepareService (serviceInfo, options = {}) {
}
}
function _buildServiceFromFile (tempServicePath, tempBundlePath, filePath) {
try {
extractServiceBundle(filePath, tempServicePath);
} catch (e) {
_buildServiceBundleFromScript(tempServicePath, tempBundlePath, fs.readFileSync(filePath));
return;
}
fs.copyFile(filePath, tempBundlePath);
}
function _buildServiceBundleFromScript (tempServicePath, tempBundlePath, jsBuffer) {
const manifest = JSON.stringify({main: 'index.js'}, null, 4);
fs.makeDirectoryRecursive(tempServicePath);
fs.writeFileSync(path.join(tempServicePath, 'index.js'), jsBuffer);
fs.writeFileSync(path.join(tempServicePath, 'manifest.json'), manifest);
utils.zipDirectory(tempServicePath, tempBundlePath);
}
function _buildServiceInPath (mount, tempServicePath, tempBundlePath) {
const servicePath = FoxxService.basePath(mount);
if (fs.exists(servicePath)) {

View File

@ -103,12 +103,11 @@ module.exports =
definition.mount,
definition.noisy
);
FoxxService.validateServiceFiles(definition.mount, manifest);
FoxxService.validateServiceFiles(basePath, manifest);
return manifest;
}
static validateServiceFiles (mount, manifest, rev) {
const servicePath = FoxxService.basePath(mount);
static validateServiceFiles (servicePath, manifest, rev) {
if (manifest.main) {
parseFile(servicePath, manifest.main);
}