Skip to content

Commit

Permalink
fix up the JS UPA api to match the python
Browse files Browse the repository at this point in the history
  • Loading branch information
briehl committed Nov 7, 2017
1 parent 5641eff commit 1d7f815
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 49 deletions.
83 changes: 55 additions & 28 deletions kbase-extension/static/kbase/js/api/upa.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
define([
], function() {
'common/runtime'
], function(Runtime) {
'use strict';

var UpaApi = function(mainWorkspace) {
var externalTag = '&',
mainWs = String(mainWorkspace);
var UpaApi = function() {
var externalTag = '&';

/**
* Runs a regex that tests the given string to see if it's a valid upa.
Expand All @@ -15,6 +15,22 @@ define([
return RegExp(/^\d+(\/\d+){2}(;\d+(\/\d+){2})*$/).test(upa);
};

var prepareUpaSerialization = function(upa) {
if (typeof upa !== 'string') {
// stringify the array version of an UPA, if that's what we have.
if (Array.isArray(upa)) {
upa = upa.join(';');
}
else {
throw {error: 'Can only serialize UPA strings or Arrays of UPA paths'};
}
}
if (!isUpa(upa)) {
throw {error: '"' + upa + '" is not a valid UPA. It may already have been serialized.'};
}
return upa;
};

/**
* @method
* @public
Expand All @@ -27,32 +43,31 @@ define([
* to
* [ws1]/obj1/ver1;ws2/obj2/ver2;...
*
* If the passed upa is not properly formatted, this will throw an Error.
*/
var serialize = function(upa) {
upa = prepareUpaSerialization(upa);
return upa.replace(/^(\d+)\//, '[$1]/');
};

/**
* @public
* @method
*
* In the case of UPAs representing objects that are located in a different workspace all
* together (e.g. set items that aren't copied into the Narrative with the set container
* object), they get flagged with a special character. In that case, the UPA is maintained,
* but transformed into:
* &ws1/obj1/ver1;ws2/obj2/ver2;...
*
* This is an explicit method for handling that serialization. Deserialization of both is handled
* by the deserialize function.
*
* If the passed upa is not properly formatted, this will throw an Error.
*/
var serialize = function(upa) {
if (typeof upa !== 'string') {
// stringify the array version of an UPA, if that's what we have.
if (Array.isArray(upa)) {
upa = upa.join(';');
}
else {
throw new Error('Can only serialize UPA strings or Arrays of UPA paths');
}
}
if (!isUpa(upa)) {
throw new Error('"' + upa + '" is not a valid UPA. It may already have been serialized.');
}
var headWs = upa.match(/^\d+/)[0];
if (headWs === mainWorkspace) {
return upa.replace(/^(\d+)/, '[$1]');
}
else {
return externalTag + upa;
}
var serializeExternal = function(upa) {
upa = prepareUpaSerialization(upa);
return externalTag + upa;
};

/**
Expand All @@ -69,16 +84,26 @@ define([
*/
var deserialize = function(serial) {
if (typeof serial !== 'string') {
throw new Error('Can only deserialize UPAs from strings.');
throw {
error: 'Can only deserialize UPAs from strings.'
};
}
var deserial;
if (serial[0] === externalTag) {
deserial = serial.substring(externalTag.length);
} else {
deserial = serial.replace(/^\[\d+\]/, mainWs);
var wsId = Runtime.make().workspaceId();
if (!wsId) {
throw {
error: 'Currently loaded workspace is unknown! Unable to deserialize UPA.'
};
}
deserial = serial.replace(/^\[\d+\]\//, Runtime.make().workspaceId() + '/');
}
if (!isUpa(deserial)) {
throw new Error('deserialized UPA: ' + deserial + ' is invalid!');
throw {
error: 'Deserialized UPA: ' + deserial + ' is invalid!'
};
}
return deserial;
};
Expand All @@ -87,7 +112,9 @@ define([
return {
serialize: serialize,
deserialize: deserialize,
externalTag: externalTag
serializeExternal: serializeExternal,
externalTag: externalTag,
isUpa: isUpa
};
};

Expand Down
2 changes: 1 addition & 1 deletion test/unit/karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = function (config) {
'kbase-extension/static/kbase/js/**/*.js': ['coverage']
},
files: [
// {pattern: 'test/unit/spec/api/fileStagingSpec.js', included: false},
// {pattern: 'test/unit/spec/api/upaApiSpec.js', included: false},
{pattern: 'test/unit/spec/**/*.js', included: false},
{pattern: 'kbase-extension/static/**/*.css', included: false, served: true},
{pattern: 'kbase-extension/static/kbase/templates/**/*.html', included: false, served: true},
Expand Down
128 changes: 108 additions & 20 deletions test/unit/spec/api/upaApiSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,8 @@ define ([
) {
'use strict';
describe('Test the UPA API', function() {

var mainWorkspace = '31',
upaApi,
testData = [{
var upaApi = new UpaApi(),
serializeTestData = [{
upa: '31/2/3',
serial: '[31]/2/3'
}, {
Expand All @@ -26,52 +24,142 @@ define ([
}, {
upa: ['31/2/3', '4/5/6', '7/8/9'],
serial: '[31]/2/3;4/5/6;7/8/9'
}];
}, {
upa: '31/31/31;31/31/31',
serial: '[31]/31/31;31/31/31'
}],
serializeExternalTestData = [{
upa: '5/1/2',
serial: upaApi.externalTag + '5/1/2'
}, {
upa: '5/1/2;1/2/3',
serial: upaApi.externalTag + '5/1/2;1/2/3'
}, {
upa: '5/5/5;1/1/1',
serial: upaApi.externalTag + '5/5/5;1/1/1'
}],
badUpas = [
'1/2',
'1/2/3;4/5/',
'1/2/a',
'123/456/7/',
'1/2/3;',
'x/y/z',
'1',
'foo',
'foo/bar',
'1;2;3',
'foo/bar;baz/frobozz',
'myws/myobj/myver;otherws/otherobj/otherver',
'myws/myobj/myver'
],
badSerials = [
'[1]/2',
'[1]/2/3;4/5/',
'[1]/2/a',
'[123]/456/7/',
'[1]/2/3;',
'[x]/y/z',
'[1]',
'[foo]',
'[foo]/bar',
'[1];2;3',
'[f]oo/bar;baz/frobozz',
'[myws]/myobj/myver;otherws/otherobj/otherver',
'[myws]/myobj/myver',
'[1]2/23/4',
'[1]2/3/4;5/6/7'
];

beforeEach(function() {
upaApi = new UpaApi(mainWorkspace);
beforeEach(function () {
history.pushState(null, null, '/narrative/ws.31.obj.1');
});

it('Should properly serialize an UPA from this workspace', function () {
testData.forEach(function(pair) {
serializeTestData.forEach(function(pair) {
expect(upaApi.serialize(pair.upa)).toBe(pair.serial);
});
});

it('Should properly deserialize an UPA from this workspace', function () {
testData.forEach(function(pair) {
serializeTestData.forEach(function(pair) {
if (typeof pair.upa === 'string') {
expect(upaApi.deserialize(pair.serial)).toBe(pair.upa);
}
});
});

it('Should serialize an UPA from a different workspace', function () {
var upa = '1/2/3';
expect(upaApi.serialize(upa)).toBe(upaApi.externalTag + upa);
serializeExternalTestData.forEach(function(pair) {
expect(upaApi.serializeExternal(pair.upa)).toBe(pair.serial);
});
});

it('Should deserialize an UPA from a different workspace', function() {
var upa = '1/2/3';
var externalUpa = upaApi.externalTag + upa;
expect(upaApi.deserialize(externalUpa)).toBe(upa);
serializeExternalTestData.forEach(function(pair) {
expect(upaApi.deserialize(pair.serial)).toBe(pair.upa);
});
});

it('Should fail to serialize a bad UPA', function () {
badUpas.forEach(function(badUpa) {
try {
upaApi.serialize(badUpa);
fail('Should have thrown an error here!');
} catch (error) {
expect(error).not.toBeNull();
expect(error.error).toEqual('"' + badUpa + '" is not a valid UPA. It may already have been serialized.');
}
});
});

it('Should fail to deserialize a bad UPA', function () {
badSerials.forEach(function(badSerial) {
try {
upaApi.deserialize(badSerial);
fail('Should have thrown an error here!');
} catch(error) {
expect(error).not.toBeNull();
expect(error.error).toMatch(/Deserialized UPA: .+ is invalid!$/);
}
});
});

it('Should fail to deserialize an UPA that is not a string', function () {
var badTypes = [
['123/4/5', '6/7/8'],
{'123': '456'},
null
];
badTypes.forEach(function(badType) {
try {
upaApi.deserialize(badType);
fail('Should have thrown an error here!');
} catch (error) {
expect(error).not.toBeNull();
expect(error.error).toEqual('Can only deserialize UPAs from strings.');
}
});
});

it('Should fail if the workspace id cannot be found.', function () {
history.pushState(null, null, '/narrative/');
try {
upaApi.serialize('not_an_upa');
fail('Should have thrown an error here!');
upaApi.deserialize('[1]/2/3');
fail('Should have failed here!');
} catch (error) {
expect(error).not.toBeNull();
expect(error.error).toEqual('Currently loaded workspace is unknown! Unable to deserialize UPA.');
}
});

it('Should fail to deserialize a bad UPA', function () {
it('Should fail to serialize an object', function () {
try {
upaApi.deserialize('not_a_serial_upa');
fail('Should have thrown an error here!');
} catch(error) {
upaApi.serialize({foo: 'bar'});
fail('Should have failed here!');
} catch (error) {
expect(error).not.toBeNull();
expect(error.error).toEqual('Can only serialize UPA strings or Arrays of UPA paths');
}
});
});
Expand Down

0 comments on commit 1d7f815

Please sign in to comment.