applinks: add tests
This commit is contained in:
+123
-91
@@ -7,7 +7,6 @@ exports = module.exports = {
|
||||
get,
|
||||
update,
|
||||
del,
|
||||
getIcon
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
@@ -55,6 +54,25 @@ function validateUpstreamUri(upstreamUri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateAccessRestriction(accessRestriction) {
|
||||
assert.strictEqual(typeof accessRestriction, 'object');
|
||||
|
||||
if (accessRestriction === null) return null;
|
||||
|
||||
if (accessRestriction.users) {
|
||||
if (!Array.isArray(accessRestriction.users)) return new BoxError(BoxError.BAD_FIELD, 'users array property required');
|
||||
if (!accessRestriction.users.every(function (e) { return typeof e === 'string'; })) return new BoxError(BoxError.BAD_FIELD, 'All users have to be strings');
|
||||
}
|
||||
|
||||
if (accessRestriction.groups) {
|
||||
if (!Array.isArray(accessRestriction.groups)) return new BoxError(BoxError.BAD_FIELD, 'groups array property required');
|
||||
if (!accessRestriction.groups.every(function (e) { return typeof e === 'string'; })) return new BoxError(BoxError.BAD_FIELD, 'All groups have to be strings');
|
||||
}
|
||||
|
||||
// TODO: maybe validate if the users and groups actually exist
|
||||
return null;
|
||||
}
|
||||
|
||||
async function list() {
|
||||
const results = await database.query(`SELECT ${APPLINKS_FIELDS} FROM applinks ORDER BY upstreamUri`);
|
||||
|
||||
@@ -67,20 +85,18 @@ async function listByUser(user) {
|
||||
assert.strictEqual(typeof user, 'object');
|
||||
|
||||
const result = await list();
|
||||
return result.filter((app) => apps.canAccess(app, user));
|
||||
return result.filter((link) => apps.canAccess(link, user));
|
||||
}
|
||||
|
||||
async function detectMetaInfo(applink) {
|
||||
assert.strictEqual(typeof applink, 'object');
|
||||
async function detectMetaInfo(upstreamUri) {
|
||||
assert.strictEqual(typeof upstreamUri, 'string');
|
||||
|
||||
const [error, response] = await safe(superagent.get(applink.upstreamUri).set('User-Agent', 'Mozilla').timeout(10*1000));
|
||||
const [error, response] = await safe(superagent.get(upstreamUri).set('User-Agent', 'Mozilla').timeout(10*1000));
|
||||
if (error || !response.text) {
|
||||
debug('detectMetaInfo: Unable to fetch upstreamUri to detect icon and title', error.statusCode);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (applink.favicon && applink.label) return;
|
||||
|
||||
// set redirected URI if any for favicon url
|
||||
const redirectUri = (response.redirects && response.redirects.length) ? response.redirects[0] : null;
|
||||
|
||||
@@ -89,73 +105,84 @@ async function detectMetaInfo(applink) {
|
||||
// No-op to skip console errors.
|
||||
});
|
||||
|
||||
const [jsdomError, dom] = await safe(jsdom.JSDOM.fromURL(applink.upstreamUri, { virtualConsole }));
|
||||
if (jsdomError) console.error('detectMetaInfo: jsdomError', jsdomError);
|
||||
|
||||
if (!applink.icon && dom) {
|
||||
let favicon = '';
|
||||
if (dom.window.document.querySelector('link[rel="apple-touch-icon"]')) favicon = dom.window.document.querySelector('link[rel="apple-touch-icon"]').href;
|
||||
if (!favicon && dom.window.document.querySelector('meta[name="msapplication-TileImage"]')) favicon = dom.window.document.querySelector('meta[name="msapplication-TileImage"]').content;
|
||||
if (!favicon && dom.window.document.querySelector('link[rel="shortcut icon"]')) favicon = dom.window.document.querySelector('link[rel="shortcut icon"]').href;
|
||||
if (!favicon && dom.window.document.querySelector('link[rel="icon"]')) {
|
||||
let iconElements = dom.window.document.querySelectorAll('link[rel="icon"]');
|
||||
if (iconElements.length) {
|
||||
favicon = iconElements[0].href; // choose first one for a start
|
||||
|
||||
// check if we have sizes attributes and then choose the largest one
|
||||
iconElements = Array.from(iconElements).filter(function (e) {
|
||||
return e.attributes.getNamedItem('sizes') && e.attributes.getNamedItem('sizes').value;
|
||||
}).sort(function (a, b) {
|
||||
return parseInt(b.attributes.getNamedItem('sizes').value.split('x')[0]) - parseInt(a.attributes.getNamedItem('sizes').value.split('x')[0]);
|
||||
});
|
||||
if (iconElements.length) favicon = iconElements[0].href;
|
||||
}
|
||||
}
|
||||
if (!favicon && dom.window.document.querySelector('meta[itemprop="image"]')) favicon = dom.window.document.querySelector('meta[itemprop="image"]').content;
|
||||
|
||||
if (favicon) {
|
||||
favicon = new URL(favicon, redirectUri || applink.upstreamUri).toString();
|
||||
|
||||
debug(`detectMetaInfo: found icon: ${favicon}`);
|
||||
|
||||
const [error, response] = await safe(superagent.get(favicon).timeout(10*1000));
|
||||
if (error) debug(`Failed to fetch icon ${favicon}: `, error);
|
||||
else if (response.ok && response.headers['content-type'].indexOf('image') !== -1) applink.icon = response.body || response.text;
|
||||
else debug(`Failed to fetch icon ${favicon}: statusCode=${response.status}`);
|
||||
}
|
||||
|
||||
if (!favicon) {
|
||||
debug(`Unable to find a suitable icon for ${applink.upstreamUri}, try fallback favicon.ico`);
|
||||
|
||||
const [error, response] = await safe(superagent.get(`${applink.upstreamUri}/favicon.ico`).timeout(10*1000));
|
||||
if (error) debug(`Failed to fetch icon ${favicon}: `, error);
|
||||
else if (response.ok && response.headers['content-type'].indexOf('image') !== -1) applink.icon = response.body || response.text;
|
||||
else debug(`Failed to fetch icon ${favicon}: statusCode=${response.status} content type ${response.headers['content-type']}`);
|
||||
}
|
||||
const [jsdomError, dom] = await safe(jsdom.JSDOM.fromURL(upstreamUri, { virtualConsole }));
|
||||
if (jsdomError || !dom) {
|
||||
console.error('detectMetaInfo: jsdomError', jsdomError);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!applink.label) {
|
||||
if (dom.window.document.querySelector('meta[property="og:title"]')) applink.label = dom.window.document.querySelector('meta[property="og:title"]').content;
|
||||
else if (dom.window.document.querySelector('meta[property="og:site_name"]')) applink.label = dom.window.document.querySelector('meta[property="og:site_name"]').content;
|
||||
else if (dom.window.document.title) applink.label = dom.window.document.title;
|
||||
let icon = null, label = '';
|
||||
|
||||
// icon detection
|
||||
let favicon = '';
|
||||
if (dom.window.document.querySelector('link[rel="apple-touch-icon"]')) favicon = dom.window.document.querySelector('link[rel="apple-touch-icon"]').href;
|
||||
if (!favicon && dom.window.document.querySelector('meta[name="msapplication-TileImage"]')) favicon = dom.window.document.querySelector('meta[name="msapplication-TileImage"]').content;
|
||||
if (!favicon && dom.window.document.querySelector('link[rel="shortcut icon"]')) favicon = dom.window.document.querySelector('link[rel="shortcut icon"]').href;
|
||||
if (!favicon && dom.window.document.querySelector('link[rel="icon"]')) {
|
||||
let iconElements = dom.window.document.querySelectorAll('link[rel="icon"]');
|
||||
if (iconElements.length) {
|
||||
favicon = iconElements[0].href; // choose first one for a start
|
||||
|
||||
// check if we have sizes attributes and then choose the largest one
|
||||
iconElements = Array.from(iconElements).filter(function (e) {
|
||||
return e.attributes.getNamedItem('sizes') && e.attributes.getNamedItem('sizes').value;
|
||||
}).sort(function (a, b) {
|
||||
return parseInt(b.attributes.getNamedItem('sizes').value.split('x')[0]) - parseInt(a.attributes.getNamedItem('sizes').value.split('x')[0]);
|
||||
});
|
||||
if (iconElements.length) favicon = iconElements[0].href;
|
||||
}
|
||||
}
|
||||
if (!favicon && dom.window.document.querySelector('meta[itemprop="image"]')) favicon = dom.window.document.querySelector('meta[itemprop="image"]').content;
|
||||
|
||||
if (favicon) {
|
||||
favicon = new URL(favicon, redirectUri || upstreamUri).toString();
|
||||
|
||||
debug(`detectMetaInfo: found icon: ${favicon}`);
|
||||
|
||||
const [error, response] = await safe(superagent.get(favicon).timeout(10*1000));
|
||||
if (error) debug(`Failed to fetch icon ${favicon}: `, error);
|
||||
else if (response.ok && response.headers['content-type'].indexOf('image') !== -1) icon = response.body || response.text;
|
||||
else debug(`Failed to fetch icon ${favicon}: statusCode=${response.status}`);
|
||||
}
|
||||
|
||||
if (!favicon) {
|
||||
debug(`Unable to find a suitable icon for ${upstreamUri}, try fallback favicon.ico`);
|
||||
|
||||
const [error, response] = await safe(superagent.get(`${upstreamUri}/favicon.ico`).timeout(10*1000));
|
||||
if (error) debug(`Failed to fetch icon ${favicon}: `, error);
|
||||
else if (response.ok && response.headers['content-type'].indexOf('image') !== -1) icon = response.body || response.text;
|
||||
else debug(`Failed to fetch icon ${favicon}: statusCode=${response.status} content type ${response.headers['content-type']}`);
|
||||
}
|
||||
|
||||
// detect label
|
||||
if (dom.window.document.querySelector('meta[property="og:title"]')) label = dom.window.document.querySelector('meta[property="og:title"]').content;
|
||||
else if (dom.window.document.querySelector('meta[property="og:site_name"]')) label = dom.window.document.querySelector('meta[property="og:site_name"]').content;
|
||||
else if (dom.window.document.title) label = dom.window.document.title;
|
||||
|
||||
return { icon, label };
|
||||
}
|
||||
|
||||
async function add(applink) {
|
||||
assert.strictEqual(typeof applink, 'object');
|
||||
assert.strictEqual(typeof applink.upstreamUri, 'string');
|
||||
|
||||
debug(`add: ${applink.upstreamUri}`);
|
||||
|
||||
let error = validateUpstreamUri(applink.upstreamUri);
|
||||
if (error) throw error;
|
||||
|
||||
error = validateAccessRestriction(applink.accessRestriction);
|
||||
if (error) throw error;
|
||||
|
||||
if (applink.icon) {
|
||||
if (!validator.isBase64(applink.icon)) throw new BoxError(BoxError.BAD_FIELD, 'icon is not base64');
|
||||
applink.icon = Buffer.from(applink.icon, 'base64');
|
||||
}
|
||||
|
||||
await detectMetaInfo(applink);
|
||||
if (!applink.icon || !applink.label) {
|
||||
const meta = await detectMetaInfo(applink.upstreamUri);
|
||||
if (!applink.label) applink.label = meta?.label;
|
||||
if (!applink.icon) applink.icon = meta?.icon;
|
||||
}
|
||||
|
||||
const data = {
|
||||
id: uuid.v4(),
|
||||
@@ -186,49 +213,54 @@ async function get(applinkId) {
|
||||
return result[0];
|
||||
}
|
||||
|
||||
async function update(applinkId, applink) {
|
||||
assert.strictEqual(typeof applinkId, 'string');
|
||||
async function update(applink, data) {
|
||||
assert.strictEqual(typeof applink, 'object');
|
||||
assert.strictEqual(typeof applink.upstreamUri, 'string');
|
||||
assert.strictEqual(typeof data, 'object');
|
||||
|
||||
debug(`update: ${applink.upstreamUri}`);
|
||||
|
||||
const error = validateUpstreamUri(applink.upstreamUri);
|
||||
if (error) throw error;
|
||||
|
||||
if (applink.icon) {
|
||||
if (!validator.isBase64(applink.icon)) throw new BoxError(BoxError.BAD_FIELD, 'icon is not base64');
|
||||
applink.icon = Buffer.from(applink.icon, 'base64');
|
||||
} else if (applink.icon === '') {
|
||||
// empty string means we autodetect in detectMetaInfo
|
||||
applink.icon = '';
|
||||
} else {
|
||||
// nothing changed reuse old
|
||||
const result = await get(applinkId);
|
||||
applink.icon = result.icon;
|
||||
let error;
|
||||
if ('upstreamUri' in data) {
|
||||
error = validateUpstreamUri(data.upstreamUri);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
await detectMetaInfo(applink);
|
||||
if ('accessRestriction' in data) {
|
||||
error = validateAccessRestriction(data.accessRestriction);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
const query = 'UPDATE applinks SET label=?, icon=?, upstreamUri=?, tagsJson=?, accessRestrictionJson=? WHERE id = ?';
|
||||
const args = [ applink.label, applink.icon || null, applink.upstreamUri, applink.tags ? JSON.stringify(applink.tags) : null, applink.accessRestriction ? JSON.stringify(applink.accessRestriction) : null, applinkId ];
|
||||
if ('icon' in data && data.icon) {
|
||||
if (!validator.isBase64(data.icon)) throw new BoxError(BoxError.BAD_FIELD, 'icon is not base64');
|
||||
data.icon = Buffer.from(data.icon, 'base64');
|
||||
}
|
||||
|
||||
const result = await database.query(query, args);
|
||||
// we don't track if label/icon in db is user-set or was auto detected
|
||||
if (data.upstreamUri || data.label === '' || data.icon === '') {
|
||||
const meta = await detectMetaInfo(data.upstreamUri || applink.upstreamUri);
|
||||
|
||||
if (data.label === '') data.label = meta?.label;
|
||||
if (data.icon === '') data.icon = meta?.icon;
|
||||
}
|
||||
|
||||
const args = [], fields = [];
|
||||
for (const k in data) {
|
||||
if (k === 'accessRestriction' || k === 'tags') {
|
||||
fields.push(`${k}Json = ?`);
|
||||
args.push(JSON.stringify(data[k]));
|
||||
} else {
|
||||
fields.push(k + ' = ?');
|
||||
args.push(data[k]);
|
||||
}
|
||||
}
|
||||
args.push(applink.id);
|
||||
|
||||
const [updateError, result] = await safe(database.query('UPDATE applinks SET ' + fields.join(', ') + ' WHERE id = ?', args));
|
||||
if (updateError) throw updateError;
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Applink not found');
|
||||
}
|
||||
|
||||
async function del(applinkId) {
|
||||
assert.strictEqual(typeof applinkId, 'string');
|
||||
async function del(applink) {
|
||||
assert.strictEqual(typeof applink, 'object');
|
||||
|
||||
const result = await database.query('DELETE FROM applinks WHERE id = ?', [ applinkId ]);
|
||||
const result = await database.query('DELETE FROM applinks WHERE id = ?', [ applink.id ]);
|
||||
if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Applink not found');
|
||||
}
|
||||
|
||||
async function getIcon(applinkId) {
|
||||
assert.strictEqual(typeof applinkId, 'string');
|
||||
|
||||
const applink = await get(applinkId);
|
||||
if (!applink) throw new BoxError(BoxError.NOT_FOUND, 'Applink not found');
|
||||
|
||||
return applink.icon;
|
||||
}
|
||||
|
||||
+29
-20
@@ -6,7 +6,9 @@ exports = module.exports = {
|
||||
get,
|
||||
update,
|
||||
del,
|
||||
getIcon
|
||||
getIcon,
|
||||
|
||||
load
|
||||
};
|
||||
|
||||
const assert = require('assert'),
|
||||
@@ -16,6 +18,18 @@ const assert = require('assert'),
|
||||
HttpError = require('connect-lastmile').HttpError,
|
||||
HttpSuccess = require('connect-lastmile').HttpSuccess;
|
||||
|
||||
async function load(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
const [error, result] = await safe(applinks.get(req.params.id));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
if (!result) return next(new HttpError(404, 'Applink not found'));
|
||||
|
||||
req.resource = result;
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
async function listByUser(req, res, next) {
|
||||
assert.strictEqual(typeof req.user, 'object');
|
||||
|
||||
@@ -31,50 +45,49 @@ async function listByUser(req, res, next) {
|
||||
async function add(req, res, next) {
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
// required
|
||||
if (!req.body.upstreamUri || typeof req.body.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a non-empty string'));
|
||||
if (typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
|
||||
|
||||
if ('label' in req.body && typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
|
||||
if ('tags' in req.body && !Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array with strings'));
|
||||
if ('accessRestriction' in req.body && typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
|
||||
if ('icon' in req.body && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon must be a string'));
|
||||
|
||||
const [error] = await safe(applinks.add(req.body));
|
||||
const [error, id] = await safe(applinks.add(req.body));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(201, {}));
|
||||
next(new HttpSuccess(201, { id }));
|
||||
}
|
||||
|
||||
async function get(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
const [error, result] = await safe(applinks.get(req.params.id));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
if (!result) return next(new HttpError(404, 'Applink not found'));
|
||||
|
||||
// we have a separate route for this
|
||||
delete result.icon;
|
||||
delete req.resource.icon;
|
||||
|
||||
next(new HttpSuccess(200, result));
|
||||
next(new HttpSuccess(200, req.resource));
|
||||
}
|
||||
|
||||
async function update(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
assert.strictEqual(typeof req.body, 'object');
|
||||
|
||||
if (!req.body.upstreamUri || typeof req.body.upstreamUri !== 'string') return next(new HttpError(400, 'upstreamUri must be a non-empty string'));
|
||||
if ('upstreamUri' in req.body && (!req.body.upstreamUri || typeof req.body.upstreamUri !== 'string')) return next(new HttpError(400, 'upstreamUri must be a non-empty string'));
|
||||
if ('accessRestriction' in req.body && typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
|
||||
if ('label' in req.body && typeof req.body.label !== 'string') return next(new HttpError(400, 'label must be a string'));
|
||||
if ('tags' in req.body && !Array.isArray(req.body.tags)) return next(new HttpError(400, 'tags must be an array with strings'));
|
||||
if ('accessRestriction' in req.body && typeof req.body.accessRestriction !== 'object') return next(new HttpError(400, 'accessRestriction must be an object'));
|
||||
if ('icon' in req.body && typeof req.body.icon !== 'string') return next(new HttpError(400, 'icon must be a string'));
|
||||
|
||||
const [error] = await safe(applinks.update(req.params.id, req.body));
|
||||
const [error] = await safe(applinks.update(req.resource, req.body));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(202, {}));
|
||||
next(new HttpSuccess(200, {}));
|
||||
}
|
||||
|
||||
async function del(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
const [error] = await safe(applinks.del(req.params.id));
|
||||
const [error] = await safe(applinks.del(req.resource));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(204));
|
||||
@@ -83,9 +96,5 @@ async function del(req, res, next) {
|
||||
async function getIcon(req, res, next) {
|
||||
assert.strictEqual(typeof req.params.id, 'string');
|
||||
|
||||
const [error, icon] = await safe(applinks.getIcon(req.params.id, { original: req.query.original }));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
if (!icon) return next(new HttpError(404, 'no such icon'));
|
||||
|
||||
res.send(icon);
|
||||
res.send(req.resource.icon);
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ async function setName(req, res, next) {
|
||||
const [error] = await safe(groups.setName(req.resource, req.body.name, AuditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { }));
|
||||
next(new HttpSuccess(200, {}));
|
||||
}
|
||||
|
||||
async function setMembers(req, res, next) {
|
||||
@@ -69,7 +69,7 @@ async function setMembers(req, res, next) {
|
||||
const [error] = await safe(groups.setMembers(req.resource, req.body.userIds, { skipSourceCheck: false }, AuditSource.fromRequest(req)));
|
||||
if (error) return next(BoxError.toHttpError(error));
|
||||
|
||||
next(new HttpSuccess(200, { }));
|
||||
next(new HttpSuccess(200, {}));
|
||||
}
|
||||
|
||||
async function list(req, res, next) {
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
'use strict';
|
||||
|
||||
/* global it:false */
|
||||
/* global describe:false */
|
||||
/* global before:false */
|
||||
/* global after:false */
|
||||
|
||||
const applinks = require('../../applinks.js'),
|
||||
common = require('./common.js'),
|
||||
expect = require('expect.js'),
|
||||
superagent = require('superagent');
|
||||
|
||||
describe('AppLinks API', function () {
|
||||
const { setup, cleanup, serverUrl, owner } = common;
|
||||
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
let applinkId;
|
||||
|
||||
it('can add applink', async function () {
|
||||
const response = await superagent.post(`${serverUrl}/api/v1/applinks`)
|
||||
.query({ access_token: owner.token })
|
||||
.send({ label: 'Berlin', tags: ['city'], upstreamUri: 'https://www.berlin.de', accessRestriction: null });
|
||||
expect(response.statusCode).to.equal(201);
|
||||
expect(response.body.id).to.be.ok();
|
||||
|
||||
applinkId = response.body.id;
|
||||
});
|
||||
|
||||
it('cannot get random applink', async function () {
|
||||
const response = await superagent.get(`${serverUrl}/api/v1/applinks/random`)
|
||||
.query({ access_token: owner.token })
|
||||
.ok(() => true);
|
||||
expect(response.statusCode).to.equal(404);
|
||||
});
|
||||
|
||||
it('cannot get valid applink', async function () {
|
||||
const response = await superagent.get(`${serverUrl}/api/v1/applinks/${applinkId}`)
|
||||
.query({ access_token: owner.token })
|
||||
.ok(() => true);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.body.upstreamUri).to.be('https://www.berlin.de');
|
||||
});
|
||||
|
||||
it('can get get icon', async function () {
|
||||
const response = await superagent.get(`${serverUrl}/api/v1/applinks/${applinkId}/icon`)
|
||||
.query({ access_token: owner.token })
|
||||
.ok(() => true);
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.headers['content-type']).to.be('application/octet-stream');
|
||||
});
|
||||
|
||||
it('can update applink tags', async function () {
|
||||
const response = await superagent.post(`${serverUrl}/api/v1/applinks/${applinkId}`)
|
||||
.query({ access_token: owner.token })
|
||||
.send({ tags: ['city', 'germany'] });
|
||||
expect(response.statusCode).to.equal(200);
|
||||
|
||||
const result = await applinks.get(applinkId);
|
||||
expect(result.tags).to.eql(['city', 'germany']);
|
||||
});
|
||||
|
||||
it('can list applinks', async function () {
|
||||
const response = await superagent.get(`${serverUrl}/api/v1/applinks`)
|
||||
.query({ access_token: owner.token });
|
||||
expect(response.statusCode).to.equal(200);
|
||||
expect(response.body.applinks.length).to.equal(1);
|
||||
expect(response.body.applinks[0].upstreamUri).to.be('https://www.berlin.de');
|
||||
});
|
||||
|
||||
it('cannot del random applink', async function () {
|
||||
const response = await superagent.del(`${serverUrl}/api/v1/applinks/random`)
|
||||
.query({ access_token: owner.token })
|
||||
.ok(() => true);
|
||||
expect(response.statusCode).to.equal(404);
|
||||
});
|
||||
|
||||
it('can del applink', async function () {
|
||||
const response = await superagent.del(`${serverUrl}/api/v1/applinks/${applinkId}`)
|
||||
.query({ access_token: owner.token })
|
||||
.ok(() => true);
|
||||
expect(response.statusCode).to.equal(204);
|
||||
|
||||
const result = await applinks.get(applinkId);
|
||||
expect(result).to.be(null);
|
||||
});
|
||||
});
|
||||
+6
-6
@@ -296,12 +296,12 @@ async function initializeExpressSync() {
|
||||
router.get ('/api/v1/apps/:id/exec/:execId/startws', token, routes.apps.load, authorizeOperator, routes.apps.startExecWebSocket);
|
||||
|
||||
// app links in dashboard
|
||||
router.get ('/api/v1/applinks', token, authorizeUser, routes.applinks.listByUser);
|
||||
router.post('/api/v1/applinks', json, token, authorizeAdmin, routes.applinks.add);
|
||||
router.get ('/api/v1/applinks/:id', token, authorizeAdmin, routes.applinks.get);
|
||||
router.post('/api/v1/applinks/:id', json, token, authorizeAdmin, routes.applinks.update);
|
||||
router.del ('/api/v1/applinks/:id', token, authorizeAdmin, routes.applinks.del);
|
||||
router.get ('/api/v1/applinks/:id/icon', token, authorizeUser, routes.applinks.getIcon);
|
||||
router.get ('/api/v1/applinks', token, authorizeUser, routes.applinks.listByUser);
|
||||
router.post('/api/v1/applinks', json, token, authorizeAdmin, routes.applinks.add);
|
||||
router.get ('/api/v1/applinks/:id', token, routes.applinks.load, authorizeAdmin, routes.applinks.get);
|
||||
router.post('/api/v1/applinks/:id', json, token, routes.applinks.load, authorizeAdmin, routes.applinks.update);
|
||||
router.del ('/api/v1/applinks/:id', token, routes.applinks.load, authorizeAdmin, routes.applinks.del);
|
||||
router.get ('/api/v1/applinks/:id/icon', token, routes.applinks.load, authorizeUser, routes.applinks.getIcon);
|
||||
|
||||
// branding routes
|
||||
router.get ('/api/v1/branding/cloudron_name', token, authorizeAdmin, routes.branding.getCloudronName);
|
||||
|
||||
+19
-15
@@ -17,8 +17,13 @@ describe('Applinks', function () {
|
||||
before(setup);
|
||||
after(cleanup);
|
||||
|
||||
function deepCopy(x) {
|
||||
return JSON.parse(JSON.stringify(x));
|
||||
}
|
||||
|
||||
const APPLINK_0 = {
|
||||
upstreamUri: 'https://cloudron.io'
|
||||
upstreamUri: 'https://cloudron.io',
|
||||
accessRestriction: null
|
||||
};
|
||||
|
||||
const APPLINK_1 = {
|
||||
@@ -30,23 +35,25 @@ describe('Applinks', function () {
|
||||
};
|
||||
|
||||
const APPLINK_2 = {
|
||||
upstreamUri: 'https://google.com'
|
||||
upstreamUri: 'https://google.com',
|
||||
accessRestriction: null
|
||||
};
|
||||
|
||||
const APPLINK_3 = {
|
||||
upstreamUri: 'http://example.com'
|
||||
upstreamUri: 'http://example.com',
|
||||
accessRestriction: null
|
||||
};
|
||||
|
||||
it('can add applink with redirect', async function () {
|
||||
APPLINK_0.id = await applinks.add(JSON.parse(JSON.stringify(APPLINK_0)));
|
||||
APPLINK_0.id = await applinks.add(deepCopy(APPLINK_0));
|
||||
});
|
||||
|
||||
it('can add second applink with attributes', async function () {
|
||||
APPLINK_1.id = await applinks.add(JSON.parse(JSON.stringify(APPLINK_1)));
|
||||
APPLINK_1.id = await applinks.add(deepCopy(APPLINK_1));
|
||||
});
|
||||
|
||||
it('can add third applink to test google.com favicon', async function () {
|
||||
APPLINK_2.id = await applinks.add(JSON.parse(JSON.stringify(APPLINK_2)));
|
||||
APPLINK_2.id = await applinks.add(deepCopy(APPLINK_2));
|
||||
|
||||
const result = await applinks.get(APPLINK_2.id);
|
||||
expect(result.upstreamUri).to.eql(APPLINK_2.upstreamUri); // should not have changed
|
||||
@@ -54,7 +61,7 @@ describe('Applinks', function () {
|
||||
});
|
||||
|
||||
it('can add fourth applink to test no favicon', async function () {
|
||||
APPLINK_3.id = await applinks.add(JSON.parse(JSON.stringify(APPLINK_3)));
|
||||
APPLINK_3.id = await applinks.add(deepCopy(APPLINK_3));
|
||||
|
||||
const result = await applinks.get(APPLINK_3.id);
|
||||
expect(result.upstreamUri).to.eql('http://example.com');
|
||||
@@ -97,10 +104,7 @@ describe('Applinks', function () {
|
||||
});
|
||||
|
||||
it('can update applink', async function () {
|
||||
APPLINK_0.upstreamUri = 'https://duckduckgo.com';
|
||||
APPLINK_0.icon = APPLINK_1.icon;
|
||||
|
||||
await applinks.update(APPLINK_0.id, JSON.parse(JSON.stringify(APPLINK_0)));
|
||||
await applinks.update(APPLINK_0, { upstreamUri: 'https://duckduckgo.com', icon: APPLINK_1.icon });
|
||||
|
||||
const result = await applinks.get(APPLINK_0.id);
|
||||
expect(result.upstreamUri).to.equal('https://duckduckgo.com');
|
||||
@@ -108,18 +112,18 @@ describe('Applinks', function () {
|
||||
});
|
||||
|
||||
it('can get applink icon', async function () {
|
||||
const result = await applinks.getIcon(APPLINK_0.id);
|
||||
expect(result.toString('base64')).to.eql(APPLINK_1.icon);
|
||||
const result = await applinks.get(APPLINK_0.id);
|
||||
expect(result.icon.toString('base64')).to.eql(APPLINK_1.icon);
|
||||
});
|
||||
|
||||
it('cannot del applink with wrong id', async function () {
|
||||
const [error] = await safe(applinks.del('doesnotexist'));
|
||||
const [error] = await safe(applinks.del({ id: 'doesnotexist' }));
|
||||
expect(error).to.be.a(BoxError);
|
||||
expect(error.reason).to.eql(BoxError.NOT_FOUND);
|
||||
});
|
||||
|
||||
it('can del applink', async function () {
|
||||
await applinks.del(APPLINK_0.id);
|
||||
await applinks.del(APPLINK_0);
|
||||
|
||||
const result = await applinks.get(APPLINK_0.id);
|
||||
expect(result).to.be(null);
|
||||
|
||||
Reference in New Issue
Block a user