1 new commit in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/commits/1fa287b15af5/ Changeset: 1fa287b15af5 User: carlfeberhard Date: 2013-04-25 20:31:43 Summary: Tools API: ensure error handling on index, show; Browser tests: add api.tools module and create tests for index, show Affected #: 3 files diff -r 6eed572a8bdcd6dc9bf2fcd54360217c1f447f20 -r 1fa287b15af5dab0ac84545835752b4f87e87328 lib/galaxy/webapps/galaxy/api/tools.py --- a/lib/galaxy/webapps/galaxy/api/tools.py +++ b/lib/galaxy/webapps/galaxy/api/tools.py @@ -5,6 +5,9 @@ from galaxy.util.json import to_json_string, from_json_string from galaxy.visualization.data_providers.genome import * +import logging +log = logging.getLogger( __name__ ) + class ToolsController( BaseAPIController, UsesVisualizationMixin ): """ RESTful controller for interactions with tools. @@ -29,7 +32,12 @@ trackster = util.string_as_bool( kwds.get( 'trackster', 'False' ) ) # Create return value. - return self.app.toolbox.to_dict( trans, in_panel=in_panel, trackster=trackster ) + try: + return self.app.toolbox.to_dict( trans, in_panel=in_panel, trackster=trackster ) + except Exception, exc: + log.error( 'could not convert toolbox to dictionary: %s', str( exc ), exc_info=True ) + trans.response.status = 500 + return { 'error': str( exc ) } @web.expose_api def show( self, trans, id, **kwd ): @@ -37,7 +45,12 @@ GET /api/tools/{tool_id} Returns tool information, including parameters and inputs. """ - return self.app.toolbox.tools_by_id[ id ].to_dict( trans, for_display=True ) + try: + return self.app.toolbox.tools_by_id[ id ].to_dict( trans, for_display=True ) + except Exception, exc: + log.error( 'could not convert tool (%s) to dictionary: %s', id, str( exc ), exc_info=True ) + trans.response.status = 500 + return { 'error': str( exc ) } @web.expose_api def create( self, trans, payload, **kwd ): @@ -45,7 +58,6 @@ POST /api/tools Executes tool using specified inputs and returns tool's outputs. """ - # HACK: for now, if action is rerun, rerun tool. action = payload.get( 'action', None ) if action == 'rerun': diff -r 6eed572a8bdcd6dc9bf2fcd54360217c1f447f20 -r 1fa287b15af5dab0ac84545835752b4f87e87328 test/casperjs/api-tool-tests.js --- /dev/null +++ b/test/casperjs/api-tool-tests.js @@ -0,0 +1,222 @@ +/* Utility to load a specific page and output html, page text, or a screenshot + * Optionally wait for some time, text, or dom selector + */ +try { + //...if there's a better way - please let me know, universe + var scriptDir = require( 'system' ).args[3] + // remove the script filename + .replace( /[\w|\.|\-|_]*$/, '' ) + // if given rel. path, prepend the curr dir + .replace( /^(?!\/)/, './' ), + spaceghost = require( scriptDir + 'spaceghost' ).create({ + // script options here (can be overridden by CLI) + //verbose: true, + //logLevel: debug, + scriptDir: scriptDir + }); + +} catch( error ){ + console.debug( error ); + phantom.exit( 1 ); +} +spaceghost.start(); + +// =================================================================== SET UP +var utils = require( 'utils' ); + +var email = spaceghost.user.getRandomEmail(), + password = '123456'; +if( spaceghost.fixtureData.testUser ){ + email = spaceghost.fixtureData.testUser.email; + password = spaceghost.fixtureData.testUser.password; +} +spaceghost.user.loginOrRegisterUser( email, password ); + +function hasKeys( object, keysArray ){ + if( !utils.isObject( object ) ){ return false; } + for( var i=0; i<keysArray.length; i += 1 ){ + if( !object.hasOwnProperty( keysArray[i] ) ){ + spaceghost.debug( 'key not found: ' + keysArray[i] ); + return false; + } + } + return true; +} + +function compareObjs( obj1, where ){ + for( var key in where ){ + if( where.hasOwnProperty( key ) ){ + if( !obj1.hasOwnProperty( key ) ){ return false; } + if( obj1[ key ] !== where[ key ] ){ return false; } + } + } + return true; +} + +function findObject( objectArray, where, start ){ + start = start || 0; + for( var i=start; i<objectArray.length; i += 1 ){ + if( compareObjs( objectArray[i], where ) ){ return objectArray[i]; } + } + return null; +} + +// =================================================================== TESTS +var panelSectionKeys = [ + 'elems', 'id', 'name', 'type', 'version' + ], + panelToolKeys = [ + 'id', 'name', 'description', 'version', 'link', 'target', 'min_width', 'type' + ], + toolSummaryKeys = [ + 'id', 'name', 'description', 'version' + ], + toolDetailKeys = [ + 'id', 'name', 'description', 'version', 'inputs' + ], + toolInputKeys = [ + 'html', 'label', 'name', 'type' + // there are others, but it's not consistent across all inputs + ]; + +function attemptShowOnAllTools(){ + //NOTE: execute like: attemptShowOnAllTools.call( spaceghost ) + toolIndex = this.api.tools.index( false ); + var toolErrors = {}; + function ObjectKeySet(){ + var self = this; + function addOne( key ){ + if( !self.hasOwnProperty( key ) ){ + self[ key ] = true; + } + } + self.__add = function( obj ){ + for( var key in obj ){ + if( obj.hasOwnProperty( key ) ){ + addOne( key ); + } + } + }; + return self; + } + var set = new ObjectKeySet(); + for( i=0; i<toolIndex.length; i+=1 ){ + var tool = toolIndex[i]; + try { + toolShow = this.api.tools.show( tool.id ); + this.info( 'checking: ' + tool.id ); + for( var j=0; j<toolShow.inputs.length; j+=1 ){ + var input = toolShow.inputs[j]; + set.__add( input ); + } + } catch( err ){ + var message = JSON.parse( err.message ).error; + this.error( '\t error: ' + message ); + toolErrors[ tool.id ] = message; + } + } + this.debug( this.jsonStr( toolErrors ) ); + this.debug( this.jsonStr( set ) ); +} + +spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){ + + // ------------------------------------------------------------------------------------------- INDEX + // ........................................................................................... (defaults) + this.test.comment( 'index should get a list of tools in panel form (by default)' ); + var toolIndex = this.api.tools.index(); + //this.debug( this.jsonStr( toolIndex ) ); + this.test.assert( utils.isArray( toolIndex ), "index returned an array: length " + toolIndex.length ); + this.test.assert( toolIndex.length >= 1, 'Has at least one tool section' ); + + this.test.comment( 'index panel form should be separated into sections (by default)' ); + var firstSection = toolIndex[0]; // get data + //this.debug( this.jsonStr( firstSection ) ); + this.test.assert( hasKeys( firstSection, panelSectionKeys ), 'Has the proper keys' ); + //TODO: test form of indiv. keys + + this.test.comment( 'index sections have a list of tool "elems"' ); + this.test.assert( utils.isArray( firstSection.elems ), firstSection.name + ".elems is an array: " + + "length " + firstSection.elems.length ); + this.test.assert( firstSection.elems.length >= 1, 'Has at least one tool' ); + + var firstTool = firstSection.elems[0]; // get data + //this.debug( this.jsonStr( firstTool ) ); + this.test.assert( hasKeys( firstTool, panelToolKeys ), 'Has the proper keys' ); + + // ........................................................................................... in_panel=False + this.test.comment( 'index should get a list of all tools when in_panel=false' ); + toolIndex = this.api.tools.index( false ); + //this.debug( this.jsonStr( toolIndex ) ); + this.test.assert( utils.isArray( toolIndex ), "index returned an array: length " + toolIndex.length ); + this.test.assert( toolIndex.length >= 1, 'Has at least one tool' ); + + this.test.comment( 'index non-panel form should be a simple list of tool summaries' ); + firstSection = toolIndex[0]; + //this.debug( this.jsonStr( firstSection ) ); + this.test.assert( hasKeys( firstSection, toolSummaryKeys ), 'Has the proper keys' ); + //TODO: test uniqueness of ids + //TODO: test form of indiv. keys + + // ........................................................................................... trackster=True + this.test.comment( '(like in_panel=True) index with trackster=True should ' + + 'get a (smaller) list of tools in panel form (by default)' ); + toolIndex = this.api.tools.index( undefined, true ); + //this.debug( this.jsonStr( toolIndex ) ); + this.test.assert( utils.isArray( toolIndex ), "index returned an array: length " + toolIndex.length ); + this.test.assert( toolIndex.length >= 1, 'Has at least one tool section' ); + + this.test.comment( 'index with trackster=True should be separated into sections (by default)' ); + firstSection = toolIndex[0]; // get data + //this.debug( this.jsonStr( firstSection ) ); + this.test.assert( hasKeys( firstSection, panelSectionKeys ), 'Has the proper keys' ); + //TODO: test form of indiv. keys + + this.test.comment( 'index sections with trackster=True have a list of tool "elems"' ); + this.test.assert( utils.isArray( firstSection.elems ), firstSection.name + ".elems is an array: " + + "length " + firstSection.elems.length ); + this.test.assert( firstSection.elems.length >= 1, 'Has at least one tool' ); + + firstTool = firstSection.elems[0]; // get data + //this.debug( this.jsonStr( firstTool ) ); + this.test.assert( hasKeys( firstTool, panelToolKeys ), 'Has the proper keys' ); + + // ............................................................................ trackster=True, in_panel=False + // this yields the same as in_panel=False... + + + // ------------------------------------------------------------------------------------------- SHOW + this.test.comment( 'show should get detailed data about the tool with the given id' ); + // get the tool select first from tool index + toolIndex = this.api.tools.index(); + var selectFirst = findObject( findObject( toolIndex, { id: 'textutil' }).elems, { id: 'Show beginning1' }); + //this.debug( this.jsonStr( selectFirst ) ); + + var toolShow = this.api.tools.show( selectFirst.id ); + //this.debug( this.jsonStr( toolShow ) ); + this.test.assert( utils.isObject( toolShow ), "show returned an object" ); + this.test.assert( hasKeys( toolShow, toolDetailKeys ), 'Has the proper keys' ); + + this.test.comment( 'show data should include an array of input objects' ); + this.test.assert( utils.isArray( toolShow.inputs ), "inputs is an array: " + + "length " + toolShow.inputs.length ); + this.test.assert( toolShow.inputs.length >= 1, 'Has at least one element' ); + for( var i=0; i<toolShow.inputs.length; i += 1 ){ + var input = toolShow.inputs[i]; + this.test.comment( 'checking input #' + i + ': ' + ( input.name || '(no name)' ) ); + this.test.assert( utils.isObject( input ), "input is an object" ); + this.test.assert( hasKeys( input, toolInputKeys ), 'Has the proper keys' ); + } + //TODO: test form of indiv. keys + + + // ------------------------------------------------------------------------------------------- CREATE + // this is a method of running a job. Shouldn't that be in jobs.create? + + // ------------------------------------------------------------------------------------------- MISC + //attemptShowOnAllTools.call( spaceghost ); +}); + +// =================================================================== +spaceghost.run( function(){ +}); diff -r 6eed572a8bdcd6dc9bf2fcd54360217c1f447f20 -r 1fa287b15af5dab0ac84545835752b4f87e87328 test/casperjs/modules/api.js --- a/test/casperjs/modules/api.js +++ b/test/casperjs/modules/api.js @@ -20,6 +20,7 @@ this.histories = new HistoriesAPI( this ); this.hdas = new HDAAPI( this ); + this.tools = new ToolsAPI( this ); }; exports.API = API; @@ -219,7 +220,7 @@ }; HDAAPI.prototype.index = function index( historyId, ids ){ - this.api.spaceghost.info( 'hda.index: ' + [ historyId, ids ] ); + this.api.spaceghost.info( 'hdas.index: ' + [ historyId, ids ] ); var data = {}; if( ids ){ ids = ( utils.isArray( ids ) )?( ids.join( ',' ) ):( ids ); @@ -232,7 +233,7 @@ }; HDAAPI.prototype.show = function show( historyId, id, deleted ){ - this.api.spaceghost.info( 'hda.show: ' + [ historyId, id, (( deleted )?( 'w deleted' ):( '' )) ] ); + this.api.spaceghost.info( 'hdas.show: ' + [ historyId, id, (( deleted )?( 'w deleted' ):( '' )) ] ); id = ( id === 'most_recently_used' )?( id ):( this.api.ensureId( id ) ); deleted = deleted || false; @@ -242,7 +243,7 @@ }; HDAAPI.prototype.create = function create( historyId, payload ){ - this.api.spaceghost.info( 'hda.create: ' + [ historyId, this.api.spaceghost.jsonStr( payload ) ] ); + this.api.spaceghost.info( 'hdas.create: ' + [ historyId, this.api.spaceghost.jsonStr( payload ) ] ); // py.payload <-> ajax.data payload = this.api.ensureObject( payload ); @@ -253,7 +254,7 @@ }; HDAAPI.prototype.update = function create( historyId, id, payload ){ - this.api.spaceghost.info( 'hda.update: ' + [ historyId, id, this.api.spaceghost.jsonStr( payload ) ] ); + this.api.spaceghost.info( 'hdas.update: ' + [ historyId, id, this.api.spaceghost.jsonStr( payload ) ] ); // py.payload <-> ajax.data historyId = this.api.ensureId( historyId ); @@ -267,3 +268,53 @@ }); }; +// =================================================================== TOOLS +var ToolsAPI = function HDAAPI( api ){ + this.api = api; +}; +ToolsAPI.prototype.toString = function toString(){ + return this.api + '.ToolsAPI'; +}; + +// ------------------------------------------------------------------- +ToolsAPI.prototype.urlTpls = { + index : 'api/tools', + show : 'api/tools/%s', + create : 'api/tools' +}; + +ToolsAPI.prototype.index = function index( in_panel, trackster ){ + this.api.spaceghost.info( 'tools.index: ' + [ in_panel, trackster ] ); + var data = {}; + // in_panel defaults to true, trackster defaults to false + if( in_panel !== undefined ){ + data.in_panel = ( in_panel )?( true ):( false ); + } + if( in_panel !== undefined ){ + data.trackster = ( trackster )?( true ):( false ); + } + return this.api._ajax( utils.format( this.urlTpls.index ), { + data : data + }); +}; + +ToolsAPI.prototype.show = function show( id ){ + this.api.spaceghost.info( 'tools.show: ' + [ id ] ); + var data = {}; + + return this.api._ajax( utils.format( this.urlTpls.show, id ), { + data : data + }); +}; + +//ToolsAPI.prototype.create = function create( payload ){ +// this.api.spaceghost.info( 'tools.create: ' + [ this.api.spaceghost.jsonStr( payload ) ] ); +// +// // py.payload <-> ajax.data +// payload = this.api.ensureObject( payload ); +// return this.api._ajax( utils.format( this.urlTpls.create, this.api.ensureId( historyId ) ), { +// type : 'POST', +// data : payload +// }); +//}; + Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.