"]);return _.extend(_.clone(e.prototype.templates),{controls:g})}());return{ListPanel:e,ModelListPanel:a}});
\ No newline at end of file
diff --git a/test/unit/managers/mock.py b/test/unit/managers/mock.py
new file mode 100644
index 00000000000..9de61ff236e
--- /dev/null
+++ b/test/unit/managers/mock.py
@@ -0,0 +1,87 @@
+"""
+Mock infrastructure for testing ModelManagers.
+"""
+import sys
+import os
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy.web import security
+from galaxy import objectstore
+from galaxy.model import mapping
+from galaxy.util.bunch import Bunch
+
+
+# =============================================================================
+class MockAppConfig( Bunch ):
+ def __init__( self, **kwargs ):
+ Bunch.__init__( self, **kwargs )
+ self.security = security.SecurityHelper( id_secret='bler' )
+ self.file_path = '/tmp'
+ self.job_working_directory = '/tmp'
+ self.new_file_path = '/tmp'
+
+ self.object_store_config_file = ''
+ self.object_store = 'disk'
+ self.object_store_check_old_style = False
+
+ self.user_activation_on = False
+ self.new_user_dataset_access_role_default_private = False
+
+ self.allow_user_dataset_purge = True
+
+class MockApp( object ):
+ def __init__( self, **kwargs ):
+ self.config = MockAppConfig( **kwargs )
+ self.security = self.config.security
+ self.object_store = objectstore.build_object_store_from_config( self.config )
+ self.model = mapping.init( "/tmp", "sqlite:///:memory:", create_tables=True, object_store=self.object_store )
+ self.security_agent = self.model.security_agent
+
+class MockWebapp( object ):
+ def __init__( self, **kwargs ):
+ self.name = kwargs.get( 'name', 'galaxy' )
+
+class MockTrans( object ):
+ def __init__( self, user=None, history=None, **kwargs ):
+
+ self.app = MockApp( **kwargs )
+ self.webapp = MockWebapp( **kwargs )
+ self.sa_session = self.app.model.session
+
+ self.galaxy_session = None
+ self.__user = user
+ self.security = self.app.security
+ self.history = history
+
+ def get_user( self ):
+ if self.galaxy_session:
+ return self.galaxy_session.user
+ else:
+ return self.__user
+
+ def set_user( self, user ):
+ """Set the current user."""
+ if self.galaxy_session:
+ self.galaxy_session.user = user
+ self.sa_session.add( self.galaxy_session )
+ self.sa_session.flush()
+ self.__user = user
+
+ user = property( get_user, set_user )
+
+ def get_history( self ):
+ return self.history
+
+ def set_history( self, history ):
+ self.history = history
+
+ def fill_template( self, filename, template_lookup=None, **kwargs ):
+ template = template_lookup.get_template( filename )
+ template.output_encoding = 'utf-8'
+ return template.render( **kwargs )
diff --git a/test/unit/managers/test_DatasetManager.py b/test/unit/managers/test_DatasetManager.py
new file mode 100644
index 00000000000..6a65b601e04
--- /dev/null
+++ b/test/unit/managers/test_DatasetManager.py
@@ -0,0 +1,114 @@
+#!/usr/bin/env python
+"""
+"""
+import sys
+import os
+import pprint
+import unittest
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy import model
+from galaxy import exceptions
+from galaxy.util.bunch import Bunch
+
+import mock
+from test_ModelManager import BaseTestCase
+from galaxy.managers.datasets import DatasetManager
+
+
+# =============================================================================
+default_password = '123456'
+user2_data = dict( email='user2@user2.user2', username='user2', password=default_password )
+user3_data = dict( email='user3@user3.user3', username='user3', password=default_password )
+
+
+# =============================================================================
+class DatasetManagerTestCase( BaseTestCase ):
+
+ def set_up_managers( self ):
+ super( DatasetManagerTestCase, self ).set_up_managers()
+ self.dataset_mgr = DatasetManager( self.app )
+
+ def test_create( self ):
+ print "should be able to create a new Dataset"
+ dataset1 = self.dataset_mgr.create( self.trans )
+ self.assertIsInstance( dataset1, model.Dataset )
+ self.assertEqual( dataset1, self.trans.sa_session.query( model.Dataset ).get( dataset1.id ) )
+
+ def test_base( self ):
+ dataset1 = self.dataset_mgr.create( self.trans )
+ dataset2 = self.dataset_mgr.create( self.trans )
+
+ print "should be able to query"
+ datasets = self.trans.sa_session.query( model.Dataset ).all()
+ self.assertEqual( self.dataset_mgr.list( self.trans ), datasets )
+ self.assertEqual( self.dataset_mgr.one( self.trans, filters=( model.Dataset.id == dataset1.id ) ), dataset1 )
+ self.assertEqual( self.dataset_mgr.by_id( self.trans, dataset1.id ), dataset1 )
+ self.assertEqual( self.dataset_mgr.by_ids( self.trans, [ dataset2.id, dataset1.id ] ), [ dataset2, dataset1 ] )
+
+ print "should be able to limit and offset"
+ self.assertEqual( self.dataset_mgr.list( self.trans, limit=1 ), datasets[0:1] )
+ self.assertEqual( self.dataset_mgr.list( self.trans, offset=1 ), datasets[1:] )
+ self.assertEqual( self.dataset_mgr.list( self.trans, limit=1, offset=1 ), datasets[1:2] )
+
+ self.assertEqual( self.dataset_mgr.list( self.trans, limit=0 ), [] )
+ self.assertEqual( self.dataset_mgr.list( self.trans, offset=3 ), [] )
+
+ print "should be able to order"
+ self.assertEqual( self.dataset_mgr.list( self.trans, order_by=sqlalchemy.desc( model.Dataset.create_time ) ),
+ [ dataset2, dataset1 ] )
+
+ def test_delete( self ):
+ item1 = self.dataset_mgr.create( self.trans )
+
+ print "should be able to delete and undelete an hda"
+ self.assertFalse( item1.deleted )
+ self.assertEqual( self.dataset_mgr.delete( self.trans, item1 ), item1 )
+ self.assertTrue( item1.deleted )
+ self.assertEqual( self.dataset_mgr.undelete( self.trans, item1 ), item1 )
+ self.assertFalse( item1.deleted )
+
+ def test_purge_allowed( self ):
+ self.trans.app.config.allow_user_dataset_purge = True
+ item1 = self.dataset_mgr.create( self.trans )
+
+ print "should purge an hda if config does allow"
+ self.assertFalse( item1.purged )
+ self.assertEqual( self.dataset_mgr.purge( self.trans, item1 ), item1 )
+ self.assertTrue( item1.purged )
+
+ def test_purge_not_allowed( self ):
+ self.trans.app.config.allow_user_dataset_purge = False
+ item1 = self.dataset_mgr.create( self.trans )
+
+ print "should raise an error when purging an hda if config does not allow"
+ self.assertFalse( item1.purged )
+ self.assertRaises( exceptions.ConfigDoesNotAllowException, self.dataset_mgr.purge, self.trans, item1 )
+ self.assertFalse( item1.purged )
+
+ def test_accessible( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ dataset = self.dataset_mgr.create( self.trans )
+
+ print "(by default, dataset permissions are lax) should be accessible to all"
+ for user in self.user_mgr.list( self.trans ):
+ self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, user ) )
+
+#TODO: this is ... not working...
+ self.dataset_mgr.give_access_permission( self.trans, dataset, owner )
+ self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, owner ) )
+ self.assertFalse( self.dataset_mgr.is_accessible( self.trans, dataset, non_owner ) )
+
+
+# =============================================================================
+if __name__ == '__main__':
+ # or more generally, nosetests test_resourcemanagers.py -s -v
+ unittest.main()
diff --git a/test/unit/managers/test_HDAManager.py b/test/unit/managers/test_HDAManager.py
new file mode 100644
index 00000000000..0511391fdd3
--- /dev/null
+++ b/test/unit/managers/test_HDAManager.py
@@ -0,0 +1,198 @@
+import sys
+import os
+import pprint
+import unittest
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy import model
+from galaxy import exceptions
+from galaxy.util.bunch import Bunch
+
+import mock
+from test_ModelManager import BaseTestCase
+from galaxy.managers.histories import HistoryManager
+from galaxy.managers.datasets import DatasetManager
+from galaxy.managers.hdas import HDAManager
+
+
+# =============================================================================
+default_password = '123456'
+user2_data = dict( email='user2@user2.user2', username='user2', password=default_password )
+user3_data = dict( email='user3@user3.user3', username='user3', password=default_password )
+
+
+# =============================================================================
+class HDAManagerTestCase( BaseTestCase ):
+
+ def set_up_managers( self ):
+ super( HDAManagerTestCase, self ).set_up_managers()
+ self.history_mgr = HistoryManager( self.app )
+ self.dataset_mgr = DatasetManager( self.app )
+ self.hda_mgr = HDAManager( self.app )
+
+ def test_create( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+
+ print "should be able to create a new HDA with a specified history and dataset"
+ hda1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1, hid=1 )
+ self.assertIsInstance( hda1, model.HistoryDatasetAssociation )
+ self.assertEqual( hda1, self.trans.sa_session.query( model.HistoryDatasetAssociation ).get( hda1.id ) )
+ self.assertEqual( hda1.history, history1 )
+ self.assertEqual( hda1.dataset, dataset1 )
+ self.assertEqual( hda1.hid, 1 )
+
+ print "should be able to create a new HDA with only a specified history and no dataset"
+ hda2 = self.hda_mgr.create( self.trans, history=history1, hid=2 )
+ self.assertIsInstance( hda2, model.HistoryDatasetAssociation )
+ self.assertIsInstance( hda2.dataset, model.Dataset )
+ self.assertEqual( hda2.history, history1 )
+ self.assertEqual( hda2.hid, 2 )
+
+ print "should be able to create a new HDA with no history and no dataset"
+ hda3 = self.hda_mgr.create( self.trans )
+ self.assertIsInstance( hda3, model.HistoryDatasetAssociation )
+ self.assertIsInstance( hda3.dataset, model.Dataset, msg="dataset will be auto created" )
+ self.assertIsNone( hda3.history, msg="history will be None" )
+ self.assertEqual( hda3.hid, None )
+
+ def test_copy_from_hda( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+ hda1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1 )
+
+ print "should be able to copy an HDA"
+ hda2 = self.hda_mgr.copy_hda( self.trans, hda1, history=history1 )
+ self.assertIsInstance( hda2, model.HistoryDatasetAssociation )
+ self.assertEqual( hda2, self.trans.sa_session.query( model.HistoryDatasetAssociation ).get( hda2.id ) )
+ self.assertEqual( hda2.name, hda1.name )
+ self.assertEqual( hda2.history, hda1.history )
+ self.assertEqual( hda2.dataset, hda1.dataset )
+ self.assertNotEqual( hda2, hda1 )
+ print hda1.hid
+ print hda2.hid
+
+ #def test_copy_from_ldda( self ):
+ # owner = self.user_mgr.create( self.trans, **user2_data )
+ # history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ #
+ # print "should be able to copy an HDA"
+ # hda2 = self.hda_mgr.copy_ldda( self.trans, history1, hda1 )
+
+ def test_base( self ):
+ hda_model = model.HistoryDatasetAssociation
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ hda1 = self.hda_mgr.create( self.trans, history=history1, hid=1 )
+ hda2 = self.hda_mgr.create( self.trans, history=history1, hid=2 )
+ hda3 = self.hda_mgr.create( self.trans, history=history1, hid=3 )
+
+ print "should be able to query"
+ hdas = self.trans.sa_session.query( hda_model ).all()
+ self.assertEqual( self.hda_mgr.list( self.trans ), hdas )
+ self.assertEqual( self.hda_mgr.one( self.trans, filters=( hda_model.id == hda1.id ) ), hda1 )
+ self.assertEqual( self.hda_mgr.by_id( self.trans, hda1.id ), hda1 )
+ self.assertEqual( self.hda_mgr.by_ids( self.trans, [ hda2.id, hda1.id ] ), [ hda2, hda1 ] )
+
+ print "should be able to limit and offset"
+ self.assertEqual( self.hda_mgr.list( self.trans, limit=1 ), hdas[0:1] )
+ self.assertEqual( self.hda_mgr.list( self.trans, offset=1 ), hdas[1:] )
+ self.assertEqual( self.hda_mgr.list( self.trans, limit=1, offset=1 ), hdas[1:2] )
+
+ self.assertEqual( self.hda_mgr.list( self.trans, limit=0 ), [] )
+ self.assertEqual( self.hda_mgr.list( self.trans, offset=3 ), [] )
+
+ print "should be able to order"
+ self.assertEqual( self.hda_mgr.list( self.trans, order_by=sqlalchemy.desc( hda_model.create_time ) ),
+ [ hda3, hda2, hda1 ] )
+
+ def test_delete( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+ item1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1 )
+
+ print "should be able to delete and undelete an hda"
+ self.assertFalse( item1.deleted )
+ self.assertEqual( self.hda_mgr.delete( self.trans, item1 ), item1 )
+ self.assertTrue( item1.deleted )
+ self.assertEqual( self.hda_mgr.undelete( self.trans, item1 ), item1 )
+ self.assertFalse( item1.deleted )
+
+ def test_purge_allowed( self ):
+ self.trans.app.config.allow_user_dataset_purge = True
+
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+ item1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1 )
+
+ print "should purge an hda if config does allow"
+ self.assertFalse( item1.purged )
+ self.assertEqual( self.hda_mgr.purge( self.trans, item1 ), item1 )
+ self.assertTrue( item1.purged )
+
+ def test_purge_not_allowed( self ):
+ self.trans.app.config.allow_user_dataset_purge = False
+ self.assertRaises( exceptions.ConfigDoesNotAllowException,
+ self.hda_mgr.error_unless_dataset_purge_allowed, self.trans, None )
+
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+ item1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1 )
+
+ print "should raise an error when purging an hda if config does not allow"
+ self.assertFalse( item1.purged )
+ self.assertRaises( exceptions.ConfigDoesNotAllowException, self.hda_mgr.purge, self.trans, item1 )
+ self.assertFalse( item1.purged )
+
+ #def test_ownable( self ):
+ # owner = self.user_mgr.create( self.trans, **user2_data )
+ # non_owner = self.user_mgr.create( self.trans, **user3_data )
+ #
+ # history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ # dataset1 = self.dataset_mgr.create( self.trans )
+ # item1 = self.hda_mgr.create( self.trans, history1, dataset1 )
+ #
+ # print "should be able to poll whether a given user owns an item"
+ # self.assertTrue( self.hda_mgr.is_owner( self.trans, item1, owner ) )
+ # self.assertFalse( self.hda_mgr.is_owner( self.trans, item1, non_owner ) )
+ #
+ # print "should raise an error when checking ownership with non-owner"
+ # self.assertRaises( exceptions.ItemOwnershipException,
+ # self.hda_mgr.check_ownership, self.trans, item1, non_owner )
+ #
+ # print "should not raise an error when checking ownership with owner"
+ # self.assertEqual( self.hda_mgr.check_ownership( self.trans, item1, owner ), item1 )
+ #
+ # #print "should not raise an error when checking ownership with admin"
+ # #self.assertEqual( self.hda_mgr.check_ownership( self.trans, item1, self.admin_user ), item1 )
+
+ def test_accessible( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
+ item1 = self.hda_mgr.create( self.trans, history1, dataset1 )
+
+ print "(by default, dataset permissions are lax) should be accessible to all"
+ for user in self.user_mgr.list( self.trans ):
+ self.assertTrue( self.hda_mgr.is_accessible( self.trans, item1, user ) )
+
+
+# =============================================================================
+if __name__ == '__main__':
+ # or more generally, nosetests test_resourcemanagers.py -s -v
+ unittest.main()
diff --git a/test/unit/managers/test_HistoryManager.py b/test/unit/managers/test_HistoryManager.py
new file mode 100644
index 00000000000..9e68890fe84
--- /dev/null
+++ b/test/unit/managers/test_HistoryManager.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python
+"""
+"""
+import sys
+import os
+import pprint
+import unittest
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy import model
+from galaxy import exceptions
+from galaxy.util.bunch import Bunch
+
+import mock
+from test_ModelManager import BaseTestCase
+from galaxy.managers.histories import HistoryManager
+
+
+# =============================================================================
+default_password = '123456'
+user2_data = dict( email='user2@user2.user2', username='user2', password=default_password )
+user3_data = dict( email='user3@user3.user3', username='user3', password=default_password )
+
+
+# =============================================================================
+class HistoryManagerTestCase( BaseTestCase ):
+
+ def set_up_managers( self ):
+ super( HistoryManagerTestCase, self ).set_up_managers()
+ self.history_mgr = HistoryManager( self.app )
+
+ def test_base( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ print "should be able to create a new history"
+ history1 = self.history_mgr.create( self.trans, name='history1', user=user2 )
+ self.assertIsInstance( history1, model.History )
+ self.assertEqual( history1, self.trans.sa_session.query( model.History ).get( history1.id ) )
+ self.assertEqual( history1,
+ self.trans.sa_session.query( model.History ).filter( model.History.name == 'history1' ).one() )
+ self.assertEqual( history1,
+ self.trans.sa_session.query( model.History ).filter( model.History.user == user2 ).one() )
+
+ print "should be able to copy a history"
+ history2 = self.history_mgr.copy( self.trans, history1, user=user3 )
+ self.assertIsInstance( history2, model.History )
+ self.assertEqual( history2.user, user3 )
+ self.assertEqual( history2, self.trans.sa_session.query( model.History ).get( history2.id ) )
+ self.assertEqual( history2.name, history1.name )
+ self.assertNotEqual( history2, history1 )
+
+ print "should be able to query"
+ histories = self.trans.sa_session.query( model.History ).all()
+ self.assertEqual( self.history_mgr.one( self.trans, filters=( model.History.id == history1.id ) ), history1 )
+ self.assertEqual( self.history_mgr.list( self.trans ), histories )
+ self.assertEqual( self.history_mgr.by_id( self.trans, history1.id ), history1 )
+ self.assertEqual( self.history_mgr.by_ids( self.trans, [ history2.id, history1.id ] ), [ history2, history1 ] )
+
+ print "should be able to limit and offset"
+ self.assertEqual( self.history_mgr.list( self.trans, limit=1 ), histories[0:1] )
+ self.assertEqual( self.history_mgr.list( self.trans, offset=1 ), histories[1:] )
+ self.assertEqual( self.history_mgr.list( self.trans, limit=1, offset=1 ), histories[1:2] )
+
+ self.assertEqual( self.history_mgr.list( self.trans, limit=0 ), [] )
+ self.assertEqual( self.history_mgr.list( self.trans, offset=3 ), [] )
+
+ print "should be able to order"
+ history3 = self.history_mgr.create( self.trans, name="history3", user=user2 )
+ name_first_then_time = ( model.History.name, sqlalchemy.desc( model.History.create_time ) )
+ self.assertEqual( self.history_mgr.list( self.trans, order_by=name_first_then_time ),
+ [ history2, history1, history3 ] )
+
+ def test_has_user( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ item1 = self.history_mgr.create( self.trans, user=owner )
+ item2 = self.history_mgr.create( self.trans, user=owner )
+ item3 = self.history_mgr.create( self.trans, user=non_owner )
+
+ print "should be able to list items by user"
+ user_histories = self.history_mgr.by_user( self.trans, owner )
+ self.assertEqual( user_histories, [ item1, item2 ] )
+
+ query = self.history_mgr._query_by_user( self.trans, owner )
+ self.assertEqual( query.all(), user_histories )
+
+ def test_ownable( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ item1 = self.history_mgr.create( self.trans, user=owner )
+
+ print "should be able to poll whether a given user owns an item"
+ self.assertTrue( self.history_mgr.is_owner( self.trans, item1, owner ) )
+ self.assertFalse( self.history_mgr.is_owner( self.trans, item1, non_owner ) )
+
+ print "should raise an error when checking ownership with non-owner"
+ self.assertRaises( exceptions.ItemOwnershipException,
+ self.history_mgr.error_unless_owner, self.trans, item1, non_owner )
+ self.assertRaises( exceptions.ItemOwnershipException,
+ self.history_mgr.ownership_by_id, self.trans, item1.id, non_owner )
+
+ print "should not raise an error when checking ownership with owner"
+ self.assertEqual( self.history_mgr.error_unless_owner( self.trans, item1, owner ), item1 )
+ self.assertEqual( self.history_mgr.ownership_by_id( self.trans, item1.id, owner ), item1 )
+
+ print "should not raise an error when checking ownership with admin"
+ self.assertTrue( self.history_mgr.is_owner( self.trans, item1, self.admin_user ) )
+ self.assertEqual( self.history_mgr.error_unless_owner( self.trans, item1, self.admin_user ), item1 )
+ self.assertEqual( self.history_mgr.ownership_by_id( self.trans, item1.id, self.admin_user ), item1 )
+
+ def test_accessible( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ item1 = self.history_mgr.create( self.trans, user=owner )
+
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ print "should be inaccessible by default except to owner"
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, item1, owner ) )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, item1, self.admin_user ) )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, item1, non_owner ) )
+
+ print "should raise an error when checking accessibility with non-owner"
+ self.assertRaises( exceptions.ItemAccessibilityException,
+ self.history_mgr.error_unless_accessible, self.trans, item1, non_owner )
+ self.assertRaises( exceptions.ItemAccessibilityException,
+ self.history_mgr.accessible_by_id, self.trans, item1.id, non_owner )
+
+ print "should not raise an error when checking ownership with owner"
+ self.assertEqual( self.history_mgr.error_unless_accessible( self.trans, item1, owner ), item1 )
+ self.assertEqual( self.history_mgr.accessible_by_id( self.trans, item1.id, owner ), item1 )
+
+ print "should not raise an error when checking ownership with admin"
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, item1, self.admin_user ) )
+ self.assertEqual( self.history_mgr.error_unless_accessible( self.trans, item1, self.admin_user ), item1 )
+ self.assertEqual( self.history_mgr.accessible_by_id( self.trans, item1.id, self.admin_user ), item1 )
+
+ def test_importable( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ self.trans.set_user( owner )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ item1 = self.history_mgr.create( self.trans, user=owner )
+
+ print "should not be importable by default"
+ self.assertFalse( item1.importable )
+ self.assertIsNone( item1.slug )
+
+ print "should be able to make importable (accessible by link) to all users"
+ accessible = self.history_mgr.make_importable( self.trans, item1 )
+ self.assertEqual( accessible, item1 )
+ self.assertIsNotNone( accessible.slug )
+ self.assertTrue( accessible.importable )
+
+ for user in self.user_mgr.list( self.trans ):
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, accessible, user ) )
+
+ print "should be able to make non-importable/inaccessible again"
+ inaccessible = self.history_mgr.make_non_importable( self.trans, accessible )
+ self.assertEqual( inaccessible, accessible )
+ self.assertIsNotNone( inaccessible.slug )
+ self.assertFalse( inaccessible.importable )
+
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, inaccessible, owner ) )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, inaccessible, non_owner ) )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, inaccessible, self.admin_user ) )
+
+ def test_published( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ self.trans.set_user( owner )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ item1 = self.history_mgr.create( self.trans, user=owner )
+
+ print "should not be published by default"
+ self.assertFalse( item1.published )
+ self.assertIsNone( item1.slug )
+
+ print "should be able to publish (listed publicly) to all users"
+ published = self.history_mgr.publish( self.trans, item1 )
+ self.assertEqual( published, item1 )
+ self.assertTrue( published.published )
+ # note: publishing sets importable to true as well
+ self.assertTrue( published.importable )
+ self.assertIsNotNone( published.slug )
+
+ for user in self.user_mgr.list( self.trans ):
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, published, user ) )
+
+ print "should be able to make non-importable/inaccessible again"
+ unpublished = self.history_mgr.unpublish( self.trans, published )
+ self.assertEqual( unpublished, published )
+ self.assertFalse( unpublished.published )
+ # note: unpublishing does not make non-importable, you must explicitly do that separately
+ self.assertTrue( published.importable )
+ self.history_mgr.make_non_importable( self.trans, unpublished )
+ self.assertFalse( published.importable )
+ # note: slug still remains after unpublishing
+ self.assertIsNotNone( unpublished.slug )
+
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, unpublished, owner ) )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, unpublished, non_owner ) )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, unpublished, self.admin_user ) )
+
+ def test_sharable( self ):
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ self.trans.set_user( owner )
+ non_owner = self.user_mgr.create( self.trans, **user3_data )
+
+ item1 = self.history_mgr.create( self.trans, user=owner )
+
+ print "should be unshared by default"
+ self.assertEqual( self.history_mgr.get_share_assocs( self.trans, item1 ), [] )
+
+ print "should be able to share with specific users"
+ shared_item = self.history_mgr.share_with( self.trans, item1, non_owner )
+ self.assertEqual( shared_item, item1 )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, shared_item, non_owner ) )
+ self.assertEqual(
+ len( self.history_mgr.get_share_assocs( self.trans, shared_item ) ), 1 )
+ self.assertEqual(
+ len( self.history_mgr.get_share_assocs( self.trans, shared_item, user=non_owner ) ), 1 )
+
+ print "should be able to unshare with specific users"
+ unshared_item = self.history_mgr.unshare_with( self.trans, shared_item, non_owner )
+ self.assertEqual( shared_item, unshared_item )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, shared_item, non_owner ) )
+ self.assertEqual( self.history_mgr.get_share_assocs( self.trans, unshared_item ), [] )
+ self.assertEqual(
+ self.history_mgr.get_share_assocs( self.trans, unshared_item, user=non_owner ), [] )
+
+ #TODO: test slug formation
+
+ def test_anon( self ):
+ anon_user = None
+ self.trans.set_user( anon_user )
+
+ print "should not allow access and owner for anon user on a history by another anon user (None)"
+ anon_history1 = self.history_mgr.create( self.trans, user=None )
+ self.assertFalse( self.history_mgr.is_owner( self.trans, anon_history1, anon_user ) )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, anon_history1, anon_user ) )
+
+ print "should allow access and owner for anon user on a history if it's the session's current history"
+ anon_history2 = self.history_mgr.create( self.trans, user=anon_user )
+ self.trans.set_history( anon_history2 )
+ self.assertTrue( self.history_mgr.is_owner( self.trans, anon_history2, anon_user ) )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, anon_history2, anon_user ) )
+
+ print "should not allow owner or access for anon user on someone elses history"
+ owner = self.user_mgr.create( self.trans, **user2_data )
+ someone_elses = self.history_mgr.create( self.trans, user=owner )
+ self.assertFalse( self.history_mgr.is_owner( self.trans, someone_elses, anon_user ) )
+ self.assertFalse( self.history_mgr.is_accessible( self.trans, someone_elses, anon_user ) )
+
+ print "should allow access for anon user if the history is published or importable"
+ self.history_mgr.make_importable( self.trans, someone_elses )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, someone_elses, anon_user ) )
+ self.history_mgr.publish( self.trans, someone_elses )
+ self.assertTrue( self.history_mgr.is_accessible( self.trans, someone_elses, anon_user ) )
+
+ def test_delete_and_purge( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ self.trans.set_user( user2 )
+
+ history1 = self.history_mgr.create( self.trans, name='history1', user=user2 )
+ self.trans.set_history( history1 )
+
+ self.assertFalse( history1.deleted )
+
+ self.history_mgr.delete( self.trans, history1 )
+ self.assertTrue( history1.deleted )
+
+ self.history_mgr.undelete( self.trans, history1 )
+ self.assertFalse( history1.deleted )
+
+ history2 = self.history_mgr.create( self.trans, name='history2', user=user2 )
+ self.history_mgr.purge( self.trans, history1 )
+ self.assertTrue( history1.purged )
+
+ def test_histories( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ self.trans.set_user( user2 )
+
+ history1 = self.history_mgr.create( self.trans, name='history1', user=user2 )
+ self.trans.set_history( history1 )
+ history2 = self.history_mgr.create( self.trans, name='history2', user=user2 )
+
+ print "should be able to set or get the current history for a user"
+ self.assertEqual( self.history_mgr.get_current( self.trans ), history1 )
+ self.assertEqual( self.history_mgr.set_current( self.trans, history2 ), history2 )
+ self.assertEqual( self.history_mgr.get_current( self.trans ), history2 )
+ self.assertEqual( self.history_mgr.set_current_by_id( self.trans, history1.id ), history1 )
+ self.assertEqual( self.history_mgr.get_current( self.trans ), history1 )
+
+
+# =============================================================================
+if __name__ == '__main__':
+ # or more generally, nosetests test_resourcemanagers.py -s -v
+ unittest.main()
diff --git a/test/unit/managers/test_ModelManager.py b/test/unit/managers/test_ModelManager.py
new file mode 100644
index 00000000000..aef85ebe868
--- /dev/null
+++ b/test/unit/managers/test_ModelManager.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+"""
+"""
+from __future__ import print_function
+
+import sys
+import os
+import pprint
+import unittest
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy import model
+from galaxy import exceptions
+from galaxy.util.bunch import Bunch
+
+import mock
+from galaxy.managers.users import UserManager
+
+
+# =============================================================================
+admin_email = 'admin@admin.admin'
+admin_users = admin_email
+default_password = '123456'
+
+#def setUpModule():
+# print '=' * 20, 'begin module'
+#
+#def tearDownModule():
+# print '=' * 20, 'end module'
+
+
+# =============================================================================
+class BaseTestCase( unittest.TestCase ):
+
+ @classmethod
+ def setUpClass( cls ):
+ print( '\n', '-' * 20, 'begin class', cls )
+
+ @classmethod
+ def tearDownClass( cls ):
+ print( '\n', '-' * 20, 'end class', cls )
+
+ def __init__( self, *args ):
+ unittest.TestCase.__init__( self, *args )
+
+ def setUp( self ):
+ self.log( '.' * 20, 'begin test', self )
+ self.set_up_mocks()
+ self.set_up_managers()
+ self.set_up_trans()
+
+ def set_up_mocks( self ):
+ self.trans = mock.MockTrans( admin_users=admin_users )
+ self.app = self.trans.app
+
+ def set_up_managers( self ):
+ self.user_mgr = UserManager( self.app )
+
+ def set_up_trans( self ):
+ self.admin_user = self.user_mgr.create( self.trans,
+ email=admin_email, username='admin', password=default_password )
+ self.trans.set_user( self.admin_user )
+ self.trans.set_history( None )
+
+ def tearDown( self ):
+ self.log( '.' * 20, 'end test', self, '\n' )
+
+ def log( self, *args, **kwargs ):
+ print( *args, **kwargs )
+
+
+# =============================================================================
+if __name__ == '__main__':
+ # or more generally, nosetests test_resourcemanagers.py -s -v
+ unittest.main()
diff --git a/test/unit/managers/test_UserManager.py b/test/unit/managers/test_UserManager.py
new file mode 100644
index 00000000000..174e811fb67
--- /dev/null
+++ b/test/unit/managers/test_UserManager.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python
+"""
+"""
+import sys
+import os
+import pprint
+import unittest
+
+__GALAXY_ROOT__ = os.getcwd() + '/../../../'
+sys.path.append( __GALAXY_ROOT__ + 'lib' )
+
+from galaxy import eggs
+eggs.require( 'SQLAlchemy >= 0.4' )
+import sqlalchemy
+
+from galaxy import model
+from galaxy import exceptions
+from galaxy.util.bunch import Bunch
+
+import mock
+from test_ModelManager import BaseTestCase
+
+
+# =============================================================================
+admin_email = 'admin@admin.admin'
+admin_users = admin_email
+default_password = '123456'
+user2_data = dict( email='user2@user2.user2', username='user2', password=default_password )
+user3_data = dict( email='user3@user3.user3', username='user3', password=default_password )
+
+
+# =============================================================================
+class UserManagerTestCase( BaseTestCase ):
+
+ def test_framework( self ):
+ self.log( "(for testing) should have admin_user, and admin_user is current" )
+ self.assertEqual( self.trans.user, self.admin_user )
+
+ def test_base( self ):
+ self.log( "should be able to create a user" )
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ self.assertIsInstance( user2, model.User )
+ self.assertIsNotNone( user2.id )
+ self.assertEqual( user2.email, user2_data[ 'email' ] )
+ self.assertEqual( user2.password, default_password )
+
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ self.log( "should be able to query" )
+ users = self.trans.sa_session.query( model.User ).all()
+ self.assertEqual( self.user_mgr.list( self.trans ), users )
+
+ self.assertEqual( self.user_mgr.by_id( self.trans, user2.id ), user2 )
+ self.assertEqual( self.user_mgr.by_ids( self.trans, [ user3.id, user2.id ] ), [ user3, user2 ] )
+
+ self.log( "should be able to limit and offset" )
+ self.assertEqual( self.user_mgr.list( self.trans, limit=1 ), users[0:1] )
+ self.assertEqual( self.user_mgr.list( self.trans, offset=1 ), users[1:] )
+ self.assertEqual( self.user_mgr.list( self.trans, limit=1, offset=1 ), users[1:2] )
+
+ self.assertEqual( self.user_mgr.list( self.trans, limit=0 ), [] )
+ self.assertEqual( self.user_mgr.list( self.trans, offset=3 ), [] )
+
+ self.log( "should be able to order" )
+ self.assertEqual( self.user_mgr.list( self.trans, order_by=( sqlalchemy.desc( model.User.create_time ) ) ),
+ [ user3, user2, self.admin_user ] )
+
+ def test_invalid_create( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+
+ self.log( "emails must be unique" )
+ self.assertRaises( exceptions.Conflict, self.user_mgr.create,
+ self.trans, **dict( email='user2@user2.user2', username='user2a', password=default_password ) )
+ self.log( "usernames must be unique" )
+ self.assertRaises( exceptions.Conflict, self.user_mgr.create,
+ self.trans, **dict( email='user2a@user2.user2', username='user2', password=default_password ) )
+
+ def test_email_queries( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ self.log( "should be able to query by email" )
+ self.assertEqual( self.user_mgr.by_email( self.trans, user2_data[ 'email' ] ), user2 )
+
+ #note: sorted by email alpha
+ users = self.trans.sa_session.query( model.User ).all()
+ self.assertEqual( self.user_mgr.by_email_like( self.trans, '%@%' ), [ self.admin_user, user2, user3 ] )
+
+ def test_admin( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ self.log( "should be able to test whether admin" )
+ self.assertTrue( self.user_mgr.is_admin( self.trans, self.admin_user ) )
+ self.assertFalse( self.user_mgr.is_admin( self.trans, user2 ) )
+ self.assertEqual( self.user_mgr.admins( self.trans ), [ self.admin_user ] )
+ self.assertRaises( exceptions.AdminRequiredException, self.user_mgr.error_unless_admin, self.trans, user2 )
+ self.assertEqual( self.user_mgr.error_unless_admin( self.trans, self.admin_user ), self.admin_user )
+
+ def test_anonymous( self ):
+ anon = None
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+
+ self.log( "should be able to tell if a user is anonymous" )
+ self.assertRaises( exceptions.AuthenticationFailed, self.user_mgr.error_if_anonymous, self.trans, anon )
+ self.assertEqual( self.user_mgr.error_if_anonymous( self.trans, user2 ), user2 )
+
+ def test_current( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ self.log( "should be able to tell if a user is the current (trans) user" )
+ self.assertTrue( self.user_mgr.is_current_user( self.trans, self.admin_user ) )
+ self.assertFalse( self.user_mgr.is_current_user( self.trans, user2 ) )
+
+ def test_api_keys( self ):
+ user2 = self.user_mgr.create( self.trans, **user2_data )
+ user3 = self.user_mgr.create( self.trans, **user3_data )
+
+ self.log( "should be able to generate and retrieve api keys" )
+ user2_api_key = self.user_mgr.create_api_key( self.trans, user2 )
+ #self.assertIsInstance( user2_api_key, model.APIKeys )
+ self.assertIsInstance( user2_api_key, basestring )
+ self.assertEqual( self.user_mgr.api_key( self.trans, user2 ).key, user2_api_key )
+
+
+
+# =============================================================================
+if __name__ == '__main__':
+ # or more generally, nosetests test_resourcemanagers.py -s -v
+ unittest.main()
From 7b6b044ff32b3b959c2a826108bcdd6273cdf55e Mon Sep 17 00:00:00 2001
From: Eric Rasche
Date: Wed, 17 Dec 2014 15:25:07 -0600
Subject: [PATCH 009/473] Allow for shared secret between Galaxy and upstream
proxies
This prevents an impersonation attack available on Galaxies with command line
users and REMOTE_USER authentication. It is not uncommon to run Galaxy on a
"lab server" (or similar), where there are command line users in addition to
the Galaxy service. With REMOTE_USER authentication, Galaxy blindly (and
correctly) trusts the upstream value of REMOTE_USER, enabling such attacks as:
curl -H "REMOTE_USER: admin.user@domain.edu" http://localhost:8000/galaxy/
This changest adds a shared secret between Galaxy and the upstream proxy,
thereby preventing that attack. Only requests from the upstream proxy with the
correct secret key are recognised, everything else is tossed out with a 403
Forbidden.
This was implemented as part of the REMOTE_USER stack as the situation only
applies if that middleware is in use.
---
config/galaxy.ini.sample | 7 ++++
config/tool_shed.ini.sample | 7 ++++
lib/galaxy/config.py | 1 +
lib/galaxy/util/__init__.py | 2 ++
.../web/framework/middleware/remoteuser.py | 32 ++++++++++++++++-
lib/galaxy/webapps/galaxy/buildapp.py | 4 +--
lib/galaxy/webapps/tool_shed/buildapp.py | 3 +-
lib/galaxy/webapps/tool_shed/config.py | 1 +
.../framework/middleware/remoteuser.py | 34 ++++++++++++++++++-
9 files changed, 86 insertions(+), 5 deletions(-)
diff --git a/config/galaxy.ini.sample b/config/galaxy.ini.sample
index b5da4842240..8c7a602203b 100644
--- a/config/galaxy.ini.sample
+++ b/config/galaxy.ini.sample
@@ -713,6 +713,13 @@ use_interactive = True
# *not* include 'HTTP_' at the beginning of the header name.
#remote_user_header = HTTP_REMOTE_USER
+# If use_remote_user is enabled, anyone who can log in to the Galaxy host may
+# impersonate any other user by simply sending the appropriate header. Thus a
+# secret shared between the upstream proxy server, and Galaxy is required.
+# If anyone other than the Galaxy user is using the server, then apache/nginx should
+# pass a value in the header 'GX_SECRET' that is identical the one below
+#remote_user_secret = USING THE DEFAULT IS NOT SECURE!
+
# If use_remote_user is enabled, you can set this to a URL that will log your
# users out.
#remote_user_logout_href = None
diff --git a/config/tool_shed.ini.sample b/config/tool_shed.ini.sample
index 23b15b16e65..e53b82c0e68 100644
--- a/config/tool_shed.ini.sample
+++ b/config/tool_shed.ini.sample
@@ -67,6 +67,13 @@ id_secret = changethisinproductiontoo
# https://wiki.galaxyproject.org/Admin/Config/ApacheProxy
#use_remote_user = False
+# If use_remote_user is enabled, anyone who can log in to the Galaxy host may
+# impersonate any other user by simply sending the appropriate header. Thus a
+# secret shared between the upstream proxy server, and Galaxy is required.
+# If anyone other than the Galaxy user is using the server, then apache/nginx should
+# pass a value in the header 'GX_SECRET' that is identical the one below
+#remote_user_secret = changethisinproductiontoo
+
# Configuration for debugging middleware
debug = true
use_lint = false
diff --git a/lib/galaxy/config.py b/lib/galaxy/config.py
index bf1cca19dee..85fe1297606 100644
--- a/lib/galaxy/config.py
+++ b/lib/galaxy/config.py
@@ -132,6 +132,7 @@ class Configuration( object ):
self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' )
self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None )
+ self.remote_user_secret = kwargs.get( "remote_user_secret", None )
self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) )
self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) )
diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py
index 6e957f2b07a..ed55de106bd 100644
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -1119,6 +1119,8 @@ def move_merge( source, target ):
def safe_str_cmp(a, b):
+ """safely compare two strings in a timing-attack-resistant manner
+ """
if len(a) != len(b):
return False
rv = 0
diff --git a/lib/galaxy/web/framework/middleware/remoteuser.py b/lib/galaxy/web/framework/middleware/remoteuser.py
index 40271cf4b99..3d1d2adb42a 100644
--- a/lib/galaxy/web/framework/middleware/remoteuser.py
+++ b/lib/galaxy/web/framework/middleware/remoteuser.py
@@ -3,6 +3,7 @@ Middleware for handling $REMOTE_USER if use_remote_user is enabled.
"""
import socket
+from galaxy.util import safe_str_cmp
errorpage = """
@@ -37,12 +38,13 @@ errorpage = """
class RemoteUser( object ):
- def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None ):
+ def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_header=None, remote_user_secret_header=None ):
self.app = app
self.maildomain = maildomain
self.display_servers = display_servers or []
self.admin_users = admin_users or []
self.remote_user_header = remote_user_header or 'HTTP_REMOTE_USER'
+ self.config_secret_header = remote_user_secret_header
def __call__( self, environ, start_response ):
# Allow display servers
@@ -59,6 +61,34 @@ class RemoteUser( object ):
# Rewrite* method for passing REMOTE_USER and a user is
# un-authenticated. Any other possible values need to go here as well.
path_info = environ.get('PATH_INFO', '')
+
+ # If the secret header is enabled, we expect upstream to send along some key
+ # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value
+ #
+ # This is not an ideal location for this function. The reason being
+ # that because this check is done BEFORE the REMOTE_USER check, it is
+ # possible to attack the GX_SECRET key without having correct
+ # credentials. However, that's why it's not "ideal", but it is "good
+ # enough". The only users able to exploit this are ones with access to
+ # the local system (unless Galaxy is listening on 0.0.0.0....). It
+ # seems improbable that an attacker with access to the server hosting
+ # Galaxy would not have acess to Galaxy itself, and be attempting to
+ # attack the system
+ if self.config_secret_header is not None:
+ if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header):
+ title = "Access to Galaxy is denied"
+ message = """
+ Galaxy is configured to authenticate users via an external
+ method (such as HTTP authentication in Apache), but an
+ incorrect shared secret key was provided by the
+ upstream (proxy) server.
+
Please contact your local Galaxy administrator. The
+ variable remote_user_secret and
+ GX_SECRET header must be set before you may
+ access Galaxy.
+ """
+ return self.error( start_response, title, message )
+
if not environ.get(self.remote_user_header, '(null)').startswith('(null)'):
if not environ[ self.remote_user_header ].count( '@' ):
if self.maildomain is not None:
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index 7015b23627a..ca31b096ed3 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -503,8 +503,8 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ),
display_servers = util.listify( conf.get( 'display_servers', '' ) ),
admin_users = conf.get( 'admin_users', '' ).split( ',' ),
- remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ) )
- log.debug( "Enabling 'remote user' middleware" )
+ remote_user_header = conf.get( 'remote_user_header', 'HTTP_REMOTE_USER' ),
+ remote_user_secret_header = conf.get('remote_user_secret', None) )
# The recursive middleware allows for including requests in other
# requests or forwarding of requests, all on the server side.
if asbool(conf.get('use_recursive', True)):
diff --git a/lib/galaxy/webapps/tool_shed/buildapp.py b/lib/galaxy/webapps/tool_shed/buildapp.py
index a52cbbd87dd..f69ae1e0951 100644
--- a/lib/galaxy/webapps/tool_shed/buildapp.py
+++ b/lib/galaxy/webapps/tool_shed/buildapp.py
@@ -157,7 +157,8 @@ def wrap_in_middleware( app, global_conf, **local_conf ):
from galaxy.webapps.tool_shed.framework.middleware.remoteuser import RemoteUser
app = RemoteUser( app, maildomain = conf.get( 'remote_user_maildomain', None ),
display_servers = util.listify( conf.get( 'display_servers', '' ) ),
- admin_users = conf.get( 'admin_users', '' ).split( ',' ) )
+ admin_users = conf.get( 'admin_users', '' ).split( ',' ),
+ remote_user_secret_header = conf.get('remote_user_secret', None) )
log.debug( "Enabling 'remote user' middleware" )
# The recursive middleware allows for including requests in other
# requests or forwarding of requests, all on the server side.
diff --git a/lib/galaxy/webapps/tool_shed/config.py b/lib/galaxy/webapps/tool_shed/config.py
index 4d097227720..b1b8b433285 100644
--- a/lib/galaxy/webapps/tool_shed/config.py
+++ b/lib/galaxy/webapps/tool_shed/config.py
@@ -83,6 +83,7 @@ class Configuration( object ):
self.remote_user_maildomain = kwargs.get( "remote_user_maildomain", None )
self.remote_user_header = kwargs.get( "remote_user_header", 'HTTP_REMOTE_USER' )
self.remote_user_logout_href = kwargs.get( "remote_user_logout_href", None )
+ self.remote_user_secret = kwargs.get( "remote_user_secret", None )
self.require_login = string_as_bool( kwargs.get( "require_login", "False" ) )
self.allow_user_creation = string_as_bool( kwargs.get( "allow_user_creation", "True" ) )
self.allow_user_deletion = string_as_bool( kwargs.get( "allow_user_deletion", "False" ) )
diff --git a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
index cfca35e853b..8c885da383a 100644
--- a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
+++ b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
@@ -3,6 +3,7 @@ Middleware for handling $REMOTE_USER if use_remote_user is enabled.
"""
import socket
+from galaxy.util import safe_str_cmp
errorpage = """
@@ -36,11 +37,13 @@ errorpage = """
"""
class RemoteUser( object ):
- def __init__( self, app, maildomain=None, display_servers=None, admin_users=None ):
+ def __init__( self, app, maildomain=None, display_servers=None, admin_users=None, remote_user_secret_header=None ):
self.app = app
self.maildomain = maildomain
self.display_servers = display_servers or []
self.admin_users = admin_users or []
+ self.config_secret_header = remote_user_secret_header
+
def __call__( self, environ, start_response ):
environ[ 'webapp' ] = 'tool_shed'
# Allow display servers
@@ -53,6 +56,34 @@ class RemoteUser( object ):
if host in self.display_servers:
environ[ 'HTTP_REMOTE_USER' ] = 'remote_display_server@%s' % ( self.maildomain or 'example.org' )
return self.app( environ, start_response )
+
+ # If the secret header is enabled, we expect upstream to send along some key
+ # in HTTP_GX_SECRET, so we'll need to compare that here to the correct value
+ #
+ # This is not an ideal location for this function. The reason being
+ # that because this check is done BEFORE the REMOTE_USER check, it is
+ # possible to attack the GX_SECRET key without having correct
+ # credentials. However, that's why it's not "ideal", but it is "good
+ # enough". The only users able to exploit this are ones with access to
+ # the local system (unless Galaxy is listening on 0.0.0.0....). It
+ # seems improbable that an attacker with access to the server hosting
+ # Galaxy would not have acess to Galaxy itself, and be attempting to
+ # attack the system
+ if self.config_secret_header is not None:
+ if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header):
+ title = "Access to Galaxy is denied"
+ message = """
+ Galaxy is configured to authenticate users via an external
+ method (such as HTTP authentication in Apache), but an
+ incorrect shared secret key was provided by the
+ upstream (proxy) server.
+
Please contact your local Galaxy administrator. The
+ variable remote_user_secret and
+ GX_SECRET header must be set before you may
+ access Galaxy.
+ """
+ return self.error( start_response, title, message )
+
# Apache sets REMOTE_USER to the string '(null)' when using the Rewrite* method for passing REMOTE_USER and a user is
# un-authenticated. Any other possible values need to go here as well.
path_info = environ.get('PATH_INFO', '')
@@ -88,6 +119,7 @@ class RemoteUser( object ):
Contact your local Galaxy tool shed administrator.
"""
return self.error( start_response, title, message )
+
def error( self, start_response, title="Access denied", message="Contact your local Galaxy tool shed administrator." ):
start_response( '403 Forbidden', [('Content-type', 'text/html')] )
return [errorpage % (title, message)]
From 269e24397db4378ba298892213a016f25e669fe5 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Thu, 18 Dec 2014 09:47:08 -0500
Subject: [PATCH 010/473] Don't choke on tool versions switches with
significant parameter changes. Just regenerate the tool state from the
supplied parameters instead - seems to still perserve parameters on the tool
form that are common between the versions because they are coming in throuh
kwd.
---
.../webapps/galaxy/controllers/tool_runner.py | 24 +++++++++++++++----
1 file changed, 19 insertions(+), 5 deletions(-)
diff --git a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py
index 22212209e92..f7b028bab2a 100644
--- a/lib/galaxy/webapps/galaxy/controllers/tool_runner.py
+++ b/lib/galaxy/webapps/galaxy/controllers/tool_runner.py
@@ -80,14 +80,28 @@ class ToolRunner( BaseUIController ):
message=message,
status=status,
redirect=redirect ) )
- params = galaxy.util.Params( kwd, sanitize = False ) #Sanitize parameters when substituting into command line via input wrappers
- #do param translation here, used by datasource tools
- if tool.input_translator:
- tool.input_translator.translate( params )
+
+ def _validated_params_for( kwd ):
+ params = galaxy.util.Params( kwd, sanitize=False ) # Sanitize parameters when substituting into command line via input wrappers
+ #do param translation here, used by datasource tools
+ if tool.input_translator:
+ tool.input_translator.translate( params )
+ return params
+
+ params = _validated_params_for( kwd )
# We may be visiting Galaxy for the first time ( e.g., sending data from UCSC ),
# so make sure to create a new history if we've never had one before.
history = tool.get_default_history_by_trans( trans, create=True )
- template, vars = tool.handle_input( trans, params.__dict__ )
+ try:
+ template, vars = tool.handle_input( trans, params.__dict__ )
+ except KeyError:
+ # This error indicates (or at least can indicate) there was a
+ # problem with the stored tool_state - it is incompatible with
+ # this variant of the tool - possibly because the tool changed
+ # or because the tool version changed.
+ del kwd[ "tool_state" ]
+ params = _validated_params_for( kwd )
+ template, vars = tool.handle_input( trans, params.__dict__ )
if len( params ) > 0:
trans.log_event( "Tool params: %s" % ( str( params ) ), tool_id=tool_id )
add_frame = AddFrameData()
From 948b8d0375c268d5680a9a77052f18b0990ad2a2 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Thu, 18 Dec 2014 10:21:42 -0500
Subject: [PATCH 011/473] Fix bug in 8d5089f2ba8183439a866026f3583c377f8bfa74.
---
lib/galaxy/workflow/run_request.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/lib/galaxy/workflow/run_request.py b/lib/galaxy/workflow/run_request.py
index 2c665473b61..86d82e341a1 100644
--- a/lib/galaxy/workflow/run_request.py
+++ b/lib/galaxy/workflow/run_request.py
@@ -56,13 +56,13 @@ def normalize_inputs(steps, inputs, inputs_by):
possible_input_keys = []
for inputs_by_el in inputs_by.split("|"):
if inputs_by_el == "step_id":
- possible_input_keys = [str( step.id )]
+ possible_input_keys.append(str( step.id ))
elif inputs_by_el == "step_index":
- possible_input_keys = [str( step.order_index )]
+ possible_input_keys.append(str( step.order_index ))
elif inputs_by_el == "step_uuid":
- possible_input_keys = [str( step.uuid )]
+ possible_input_keys.append(str( step.uuid ))
elif inputs_by_el == "name":
- possible_input_keys = [step.tool_inputs.get( 'name', None )]
+ possible_input_keys.append(step.tool_inputs.get( 'name', None ))
else:
message = "Workflow cannot be run because unexpected inputs_by value specified."
raise exceptions.MessageException( message )
From 461638a5707b17236fbf97db430b73b2961ca598 Mon Sep 17 00:00:00 2001
From: Eric Rasche
Date: Thu, 18 Dec 2014 13:43:00 -0600
Subject: [PATCH 012/473] Fixed typos in documentation
---
lib/galaxy/web/framework/middleware/remoteuser.py | 2 +-
lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/lib/galaxy/web/framework/middleware/remoteuser.py b/lib/galaxy/web/framework/middleware/remoteuser.py
index 3d1d2adb42a..d338328718f 100644
--- a/lib/galaxy/web/framework/middleware/remoteuser.py
+++ b/lib/galaxy/web/framework/middleware/remoteuser.py
@@ -72,7 +72,7 @@ class RemoteUser( object ):
# enough". The only users able to exploit this are ones with access to
# the local system (unless Galaxy is listening on 0.0.0.0....). It
# seems improbable that an attacker with access to the server hosting
- # Galaxy would not have acess to Galaxy itself, and be attempting to
+ # Galaxy would not have access to Galaxy itself, and be attempting to
# attack the system
if self.config_secret_header is not None:
if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header):
diff --git a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
index 8c885da383a..70b98837cd4 100644
--- a/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
+++ b/lib/galaxy/webapps/tool_shed/framework/middleware/remoteuser.py
@@ -67,7 +67,7 @@ class RemoteUser( object ):
# enough". The only users able to exploit this are ones with access to
# the local system (unless Galaxy is listening on 0.0.0.0....). It
# seems improbable that an attacker with access to the server hosting
- # Galaxy would not have acess to Galaxy itself, and be attempting to
+ # Galaxy would not have access to Galaxy itself, and be attempting to
# attack the system
if self.config_secret_header is not None:
if not safe_str_cmp(environ.get('HTTP_GX_SECRET'), self.config_secret_header):
From 92846d0ae8464f9966913f03c06d6d39488dbb23 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Thu, 18 Dec 2014 21:50:15 -0500
Subject: [PATCH 013/473] PEP-8 fix for api/jobs.py.
---
lib/galaxy/webapps/galaxy/api/jobs.py | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py
index 5b29af07892..adc933aa619 100644
--- a/lib/galaxy/webapps/galaxy/api/jobs.py
+++ b/lib/galaxy/webapps/galaxy/api/jobs.py
@@ -27,7 +27,7 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
index( trans, state=None, tool_id=None, history_id=None, date_range_min=None, date_range_max=None, user_details=False )
* GET /api/jobs:
return jobs for current user
-
+
!! if user is admin and user_details is True, then
return jobs for all galaxy users based on filtering - this is an extended service
@@ -41,10 +41,10 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
:type user_details: boolean
:param user_details: if true, and requestor is an admin, will return external job id and user email.
-
+
:type date_range_min: string '2014-01-01'
- :param date_range_min: limit the listing of jobs to those updated on or after requested date
-
+ :param date_range_min: limit the listing of jobs to those updated on or after requested date
+
:type date_range_max: string '2014-12-31'
:param date_range_max: limit the listing of jobs to those updated on or before requested date
@@ -59,7 +59,7 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
is_extended_service = True
else:
is_extended_service = False
-
+
if is_extended_service:
query = trans.sa_session.query( trans.app.model.Job )
else:
@@ -80,7 +80,7 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
query = build_and_apply_filters( query, kwd.get( 'tool_id', None ), lambda t: trans.app.model.Job.tool_id == t )
query = build_and_apply_filters( query, kwd.get( 'tool_id_like', None ), lambda t: trans.app.model.Job.tool_id.like(t) )
-
+
query = build_and_apply_filters( query, kwd.get( 'date_range_min', None ), lambda dmin: trans.app.model.Job.table.c.update_time >= dmin )
query = build_and_apply_filters( query, kwd.get( 'date_range_max', None ), lambda dmax: trans.app.model.Job.table.c.update_time <= dmax )
@@ -98,12 +98,12 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
else:
order_by = trans.app.model.Job.update_time.desc()
for job in query.order_by( order_by ).all():
- j = self.encode_all_ids( trans, job.to_dict( 'collection' ), True )
+ j = self.encode_all_ids( trans, job.to_dict( 'collection' ), True )
if is_extended_service:
j['external_id'] = job.job_runner_external_id
j['user_email'] = job.user.email
out.append(j)
-
+
return out
@expose_api
From 285896dfe18aedeff483c4293b75cd1307382977 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Thu, 18 Dec 2014 21:50:15 -0500
Subject: [PATCH 014/473] Increase consistency between job index and show. Add
tests for new date range and history filtering as well as to ensure only
admins get to see external_id and command_line and that users cannot see each
other's jobs.
Always allow admins to views all jobs on index (instead of only when user_details is specified) and show. Add user_email and external_id to show (for admins) to bring it inline with index and add command_line to index (for admins) to bring it in line with show.
Show still allow more details including job standard error and output as well as job metrics.
---
lib/galaxy/model/__init__.py | 8 +++-
lib/galaxy/web/security/__init__.py | 2 +-
lib/galaxy/webapps/galaxy/api/jobs.py | 35 ++++++++------
test/api/test_jobs.py | 69 ++++++++++++++++++++++++---
test/base/api.py | 4 ++
test/base/api_asserts.py | 5 ++
6 files changed, 100 insertions(+), 23 deletions(-)
diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index fddd941df44..fcb1587a3e2 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -520,9 +520,15 @@ class Job( object, HasJobMetrics, Dictifiable ):
dataset.blurb = 'deleted'
dataset.peek = 'Job deleted'
dataset.info = 'Job output deleted by user before job completed'
- def to_dict( self, view='collection' ):
+
+ def to_dict( self, view='collection', system_details=False ):
rval = super( Job, self ).to_dict( view=view )
rval['tool_id'] = self.tool_id
+ if system_details:
+ # System level details that only admins should have.
+ rval['external_id'] = self.job_runner_external_id
+ rval['command_line'] = self.command_line
+
if view == 'element':
param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] )
rval['params'] = param_dict
diff --git a/lib/galaxy/web/security/__init__.py b/lib/galaxy/web/security/__init__.py
index 95998a7d21a..00a9e370050 100644
--- a/lib/galaxy/web/security/__init__.py
+++ b/lib/galaxy/web/security/__init__.py
@@ -73,7 +73,7 @@ class SecurityHelper( object ):
if not isinstance( rval, dict ):
return rval
for k, v in rval.items():
- if ( k == 'id' or k.endswith( '_id' ) ) and v is not None and k not in [ 'tool_id' ]:
+ if ( k == 'id' or k.endswith( '_id' ) ) and v is not None and k not in [ 'tool_id', 'external_id' ]:
try:
rval[ k ] = self.encode_id( v )
except Exception:
diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py
index adc933aa619..691baa73568 100644
--- a/lib/galaxy/webapps/galaxy/api/jobs.py
+++ b/lib/galaxy/webapps/galaxy/api/jobs.py
@@ -55,12 +55,10 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
:returns: list of dictionaries containing summary job information
"""
state = kwd.get( 'state', None )
- if trans.user_is_admin() and kwd.get('user_details', False):
- is_extended_service = True
- else:
- is_extended_service = False
+ is_admin = trans.user_is_admin()
+ user_details = kwd.get('user_details', False)
- if is_extended_service:
+ if is_admin:
query = trans.sa_session.query( trans.app.model.Job )
else:
query = trans.sa_session.query( trans.app.model.Job ).filter(trans.app.model.Job.user == trans.user)
@@ -98,9 +96,9 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
else:
order_by = trans.app.model.Job.update_time.desc()
for job in query.order_by( order_by ).all():
- j = self.encode_all_ids( trans, job.to_dict( 'collection' ), True )
- if is_extended_service:
- j['external_id'] = job.job_runner_external_id
+ job_dict = job.to_dict( 'collection', system_details=is_admin )
+ j = self.encode_all_ids( trans, job_dict, True )
+ if user_details:
j['user_email'] = job.user.email
out.append(j)
@@ -123,12 +121,13 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
:returns: dictionary containing full description of job data
"""
job = self.__get_job( trans, id )
- job_dict = self.encode_all_ids( trans, job.to_dict( 'element' ), True )
+ is_admin = trans.user_is_admin()
+ job_dict = self.encode_all_ids( trans, job.to_dict( 'element', system_details=is_admin ), True )
full_output = util.asbool( kwd.get( 'full', 'false' ) )
if full_output:
job_dict.update( dict( stderr=job.stderr, stdout=job.stdout ) )
- if trans.user_is_admin():
- job_dict['command_line'] = job.command_line
+ if is_admin:
+ job_dict['user_email'] = job.user.email
def metric_to_dict(metric):
metric_name = metric.metric_name
@@ -199,10 +198,16 @@ class JobController( BaseAPIController, UsesHistoryDatasetAssociationMixin, Uses
decoded_job_id = trans.security.decode_id( id )
except Exception:
raise exceptions.MalformedId()
- query = trans.sa_session.query( trans.app.model.Job ).filter(
- trans.app.model.Job.user == trans.user,
- trans.app.model.Job.id == decoded_job_id
- )
+ query = trans.sa_session.query( trans.app.model.Job )
+ if trans.user_is_admin():
+ query = query.filter(
+ trans.app.model.Job.id == decoded_job_id
+ )
+ else:
+ query = query.filter(
+ trans.app.model.Job.user == trans.user,
+ trans.app.model.Job.id == decoded_job_id
+ )
job = query.first()
if job is None:
raise exceptions.ObjectNotFound()
diff --git a/test/api/test_jobs.py b/test/api/test_jobs.py
index b962f0b914b..d5d6c3d3d51 100644
--- a/test/api/test_jobs.py
+++ b/test/api/test_jobs.py
@@ -1,4 +1,6 @@
+import datetime
import json
+import time
from operator import itemgetter
from base import api
@@ -11,14 +13,19 @@ class JobsApiTestCase( api.ApiTestCase, TestsDatasets ):
def test_index( self ):
# Create HDA to ensure at least one job exists...
self.__history_with_new_dataset()
- jobs_response = self._get( "jobs" )
-
- self._assert_status_code_is( jobs_response, 200 )
-
- jobs = jobs_response.json()
- assert isinstance( jobs, list )
+ jobs = self.__jobs_index()
assert "upload1" in map( itemgetter( "tool_id" ), jobs )
+ def test_system_details_admin_only( self ):
+ self.__history_with_new_dataset()
+ jobs = self.__jobs_index( admin=False )
+ job = jobs[0]
+ self._assert_not_has_keys( job, "command_line", "external_id" )
+
+ jobs = self.__jobs_index( admin=True )
+ job = jobs[0]
+ self._assert_has_keys( job, "command_line", "external_id" )
+
def test_index_state_filter( self ):
# Initial number of ok jobs
original_count = len( self.__uploads_with_state( "ok" ) )
@@ -31,6 +38,33 @@ class JobsApiTestCase( api.ApiTestCase, TestsDatasets ):
new_count = len( self.__uploads_with_state( "ok" ) )
assert original_count < new_count
+ def test_index_date_filter( self ):
+ self.__history_with_new_dataset()
+ two_weeks_ago = (datetime.datetime.utcnow() - datetime.timedelta(7)).isoformat()
+ last_week = (datetime.datetime.utcnow() - datetime.timedelta(7)).isoformat()
+ next_week = (datetime.datetime.utcnow() + datetime.timedelta(7)).isoformat()
+ today = datetime.datetime.utcnow().isoformat()
+ tomorrow = (datetime.datetime.utcnow() + datetime.timedelta(1)).isoformat()
+
+ jobs = self.__jobs_index( data={"date_range_min": today[0:10], "date_range_max": tomorrow[0:10]} )
+ assert len( jobs ) > 0
+ today_job_id = jobs[0]["id"]
+
+ jobs = self.__jobs_index( data={"date_range_min": two_weeks_ago, "date_range_max": last_week} )
+ assert today_job_id not in map(itemgetter("id"), jobs)
+
+ jobs = self.__jobs_index( data={"date_range_min": last_week, "date_range_max": next_week} )
+ assert today_job_id in map(itemgetter("id"), jobs)
+
+ def test_index_history( self ):
+ history_id, _ = self.__history_with_new_dataset()
+ jobs = self.__jobs_index( data={"history_id": history_id} )
+ assert len( jobs ) > 0
+
+ history_id = self._new_history()
+ jobs = self.__jobs_index( data={"history_id": history_id} )
+ assert len( jobs ) == 0
+
def test_index_multiple_states_filter( self ):
# Initial number of ok jobs
original_count = len( self.__uploads_with_state( "ok", "new" ) )
@@ -58,6 +92,22 @@ class JobsApiTestCase( api.ApiTestCase, TestsDatasets ):
job_details = show_jobs_response.json()
self._assert_has_key( job_details, 'id', 'state', 'exit_code', 'update_time', 'create_time' )
+ def test_show_security( self ):
+ history_id, _ = self.__history_with_new_dataset()
+ jobs_response = self._get( "jobs", data={"history_id": history_id} )
+ job = jobs_response.json()[ 0 ]
+ job_id = job[ "id" ]
+
+ show_jobs_response = self._get( "jobs/%s" % job_id, admin=False )
+ self._assert_not_has_keys( show_jobs_response.json(), "command_line", "external_id" )
+
+ with self._different_user():
+ show_jobs_response = self._get( "jobs/%s" % job_id, admin=False )
+ self._assert_status_code_is( show_jobs_response, 404 )
+
+ show_jobs_response = self._get( "jobs/%s" % job_id, admin=True )
+ self._assert_has_keys( show_jobs_response.json(), "command_line", "external_id" )
+
def test_search( self ):
history_id, dataset_id = self.__history_with_ok_dataset()
@@ -133,3 +183,10 @@ class JobsApiTestCase( api.ApiTestCase, TestsDatasets ):
history_id, dataset_id = self.__history_with_new_dataset()
self._wait_for_history( history_id, assert_ok=True )
return history_id, dataset_id
+
+ def __jobs_index( self, **kwds ):
+ jobs_response = self._get( "jobs", **kwds )
+ self._assert_status_code_is( jobs_response, 200 )
+ jobs = jobs_response.json()
+ assert isinstance( jobs, list )
+ return jobs
diff --git a/test/base/api.py b/test/base/api.py
index 0a4d1f4f1ab..7ef7cebca2e 100644
--- a/test/base/api.py
+++ b/test/base/api.py
@@ -11,6 +11,7 @@ from .api_util import get_user_api_key
from .api_asserts import (
assert_status_code_is,
assert_has_keys,
+ assert_not_has_keys,
assert_error_code_is,
)
@@ -83,6 +84,9 @@ class ApiTestCase( TwillTestCase ):
def _assert_has_keys( self, response, *keys ):
assert_has_keys( response, *keys )
+ def _assert_not_has_keys( self, response, *keys ):
+ assert_not_has_keys( response, *keys )
+
def _assert_error_code_is( self, response, error_code ):
assert_error_code_is( response, error_code )
diff --git a/test/base/api_asserts.py b/test/base/api_asserts.py
index 129b63fca53..12f7b554eb9 100644
--- a/test/base/api_asserts.py
+++ b/test/base/api_asserts.py
@@ -20,6 +20,11 @@ def assert_has_keys( response, *keys ):
assert key in response, "Response [%s] does not contain key [%s]" % ( response, key )
+def assert_not_has_keys( response, *keys ):
+ for key in keys:
+ assert key not in response, "Response [%s] contains invalid key [%s]" % ( response, key )
+
+
def assert_error_code_is( response, error_code ):
if hasattr( response, "json" ):
response = response.json()
From ec62c5417c28db43755d667159f794f28d4c4694 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Fri, 19 Dec 2014 09:15:27 -0500
Subject: [PATCH 015/473] Replace preferred workflow usage terminology in the
API (s/usage/invocation) `GET /api/workflows//usage` made sense
for read-only operations - but now for instance `POST
/api/workflows//usage` is the preferred method for invoking
workflows and `PUT
/api/workflows//usage//steps/` can be used to
interact with steps while the workflow is running. I think the term is a
little awkward in that sense and is incongruous with the rest of Galaxy which
uses the terminology "invocations".
In a backward compatible way this replaces the usage terminology with invocation terminology (and uses the plural in the route which is more consistent with the rest of the API - `POST /api/histories` for instance).
---
lib/galaxy/webapps/galaxy/api/workflows.py | 57 ++++++++++----------
lib/galaxy/webapps/galaxy/buildapp.py | 60 +++++++++++++++++++---
2 files changed, 83 insertions(+), 34 deletions(-)
diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py
index 7dd5afea3c0..c0f394fcbb2 100644
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -372,9 +372,9 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesHis
return item
@expose_api
- def workflow_request( self, trans, workflow_id, payload, **kwd ):
+ def invoke( self, trans, workflow_id, payload, **kwd ):
"""
- POST /api/workflows/{encoded_workflow_id}/usage
+ POST /api/workflows/{encoded_workflow_id}/invocations
Schedule the workflow specified by `workflow_id` to run.
"""
@@ -398,10 +398,11 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesHis
return self.encode_all_ids( trans, workflow_invocation.to_dict(), recursive=True )
@expose_api
- def workflow_usage(self, trans, workflow_id, **kwd):
+ def index_invocations(self, trans, workflow_id, **kwd):
"""
- GET /api/workflows/{workflow_id}/usage
- Get the list of the workflow usage
+ GET /api/workflows/{workflow_id}/invocations
+
+ Get the list of the workflow invocations
:param workflow_id: the workflow id (required)
:type workflow_id: str
@@ -416,53 +417,53 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesHis
return out
@expose_api
- def workflow_usage_contents(self, trans, workflow_id, usage_id, **kwd):
+ def show_invocation(self, trans, workflow_id, invocation_id, **kwd):
"""
- GET /api/workflows/{workflow_id}/usage/{usage_id}
- Get detailed description of workflow usage
+ GET /api/workflows/{workflow_id}/invocation/{invocation_id}
+ Get detailed description of workflow invocation
- :param workflow_id: the workflow id (required)
- :type workflow_id: str
+ :param workflow_id: the workflow id (required)
+ :type workflow_id: str
- :param usage_id: the usage id (required)
- :type usage_id: str
+ :param invocation_id: the invocation id (required)
+ :type invocation_id: str
:raises: exceptions.MessageException, exceptions.ObjectNotFound
"""
- decoded_workflow_invocation_id = self.__decode_id( trans, usage_id )
+ decoded_workflow_invocation_id = self.__decode_id( trans, invocation_id )
workflow_invocation = self.workflow_manager.get_invocation( trans, decoded_workflow_invocation_id )
if workflow_invocation:
return self.__encode_invocation( trans, workflow_invocation )
return None
@expose_api
- def cancel_workflow_invocation(self, trans, workflow_id, usage_id, **kwd):
+ def cancel_invocation(self, trans, workflow_id, invocation_id, **kwd):
"""
- DELETE /api/workflows/{workflow_id}/usage/{usage_id}
+ DELETE /api/workflows/{workflow_id}/invocation/{invocation_id}
Cancel the specified workflow invocation.
:param workflow_id: the workflow id (required)
:type workflow_id: str
- :param usage_id: the usage id (required)
- :type usage_id: str
+ :param invocation_id: the usage id (required)
+ :type invocation_id: str
:raises: exceptions.MessageException, exceptions.ObjectNotFound
"""
- decoded_workflow_invocation_id = self.__decode_id( trans, usage_id )
+ decoded_workflow_invocation_id = self.__decode_id( trans, invocation_id )
workflow_invocation = self.workflow_manager.cancel_invocation( trans, decoded_workflow_invocation_id )
return self.__encode_invocation( trans, workflow_invocation )
@expose_api
- def workflow_invocation_step(self, trans, workflow_id, usage_id, step_id, **kwd):
+ def invocation_step(self, trans, workflow_id, invocation_id, step_id, **kwd):
"""
- GET /api/workflows/{workflow_id}/usage/{usage_id}/steps/{step_id}
+ GET /api/workflows/{workflow_id}/invocation/{invocation_id}/steps/{step_id}
- :param workflow_id: the workflow id (required)
- :type workflow_id: str
+ :param workflow_id: the workflow id (required)
+ :type workflow_id: str
- :param usage_id: the usage id (required)
- :type usage_id: str
+ :param invocation_id: the invocation id (required)
+ :type invocation_id: str
:param step_id: encoded id of the WorkflowInvocationStep (required)
:type step_id: str
@@ -480,9 +481,9 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesHis
return self.__encode_invocation_step( trans, invocation_step )
@expose_api
- def workflow_invocation_step_update(self, trans, workflow_id, usage_id, step_id, payload, **kwd):
+ def update_invocation_step(self, trans, workflow_id, invocation_id, step_id, payload, **kwd):
"""
- PUT /api/workflows/{workflow_id}/usage/{usage_id}/steps/{step_id}
+ PUT /api/workflows/{workflow_id}/invocation/{invocation_id}/steps/{step_id}
Update state of running workflow step invocation - still very nebulous
but this would be for stuff like confirming paused steps can proceed
etc....
@@ -491,8 +492,8 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesHis
:param workflow_id: the workflow id (required)
:type workflow_id: str
- :param usage_id: the usage id (required)
- :type usage_id: str
+ :param invocation_id: the usage id (required)
+ :type invocation_id: str
:param step_id: encoded id of the WorkflowInvocationStep (required)
:type step_id: str
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index 7015b23627a..9864fc78c88 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -227,14 +227,62 @@ def populate_api_routes( webapp, app ):
webapp.mapper.connect( 'workflow_dict', '/api/workflows/download/{workflow_id}', controller='workflows', action='workflow_dict', conditions=dict( method=['GET'] ) )
# Deprecated in favor of POST /api/workflows with shared_workflow_id in payload.
webapp.mapper.connect( 'import_shared_workflow_deprecated', '/api/workflows/import', controller='workflows', action='import_shared_workflow_deprecated', conditions=dict( method=['POST'] ) )
- webapp.mapper.connect( 'workflow_usage', '/api/workflows/{workflow_id}/usage', controller='workflows', action='workflow_usage', conditions=dict(method=['GET']))
- webapp.mapper.connect( 'workflow_usage_contents', '/api/workflows/{workflow_id}/usage/{usage_id}', controller='workflows', action='workflow_usage_contents', conditions=dict(method=['GET']))
- webapp.mapper.connect( 'cancel_workflow_invocation', '/api/workflows/{workflow_id}/usage/{usage_id}', controller='workflows', action='cancel_workflow_invocation', conditions=dict(method=['DELETE']))
- webapp.mapper.connect( 'workflow_invocation_step', '/api/workflows/{workflow_id}/usage/{usage_id}/steps/{step_id}', controller='workflows', action='workflow_invocation_step', conditions=dict(method=['GET']))
- webapp.mapper.connect( 'workflow_invocation_step_update', '/api/workflows/{workflow_id}/usage/{usage_id}/steps/{step_id}', controller='workflows', action='workflow_invocation_step_update', conditions=dict(method=['PUT']))
+ # API refers to usages and invocations - these mean the same thing but the
+ # usage routes should be considered deprecated.
+ invoke_names = {
+ "invocations": "",
+ "usage": "_deprecated",
+ }
+ for noun, suffix in invoke_names.iteritems():
+ name = "%s%s" % (noun, suffix)
+ webapp.mapper.connect(
+ 'list_workflow_%s' % name,
+ '/api/workflows/{workflow_id}/%s' % noun,
+ controller='workflows',
+ action='index_invocations',
+ conditions=dict(method=['GET'])
+ )
- webapp.mapper.connect( 'workflow_request', '/api/workflows/{workflow_id}/usage', controller='workflows', action='workflow_request', conditions=dict( method=['POST'] ) )
+ webapp.mapper.connect(
+ 'workflow_%s_contents' % name,
+ '/api/workflows/{workflow_id}/%s/{invocation_id}' % noun,
+ controller='workflows',
+ action='show_invocation',
+ conditions=dict(method=['GET'])
+ )
+
+ webapp.mapper.connect(
+ 'cancel_workflow_%s' % name,
+ '/api/workflows/{workflow_id}/%s/{invocation_id}' % noun,
+ controller='workflows',
+ action='cancel_invocation',
+ conditions=dict(method=['DELETE'])
+ )
+
+ webapp.mapper.connect(
+ 'workflow_%s_step' % name,
+ '/api/workflows/{workflow_id}/%s/{invocation_id}/steps/{step_id}' % noun,
+ controller='workflows',
+ action='invocation_step',
+ conditions=dict(method=['GET'])
+ )
+
+ webapp.mapper.connect(
+ 'workflow_%s_step_update' % name,
+ '/api/workflows/{workflow_id}/%s/{invocation_id}/steps/{step_id}' % noun,
+ controller='workflows',
+ action='update_invocation_step',
+ conditions=dict(method=['PUT'])
+ )
+
+ webapp.mapper.connect(
+ 'workflow_%s' % name,
+ '/api/workflows/{workflow_id}/%s' % noun,
+ controller='workflows',
+ action='invoke',
+ conditions=dict( method=['POST'] )
+ )
# ============================
# ===== AUTHENTICATE API =====
# ============================
From 58f677ad4f4473d0549dd417f187cc1f7a3a9a4e Mon Sep 17 00:00:00 2001
From: Carl Eberhard
Date: Fri, 19 Dec 2014 16:56:34 -0500
Subject: [PATCH 016/473] Refining the hda manager, serializer, and
de-serializer; using same in history contents and datasets api
---
lib/galaxy/managers/base.py | 34 +-
lib/galaxy/managers/collections.py | 122 +++-
lib/galaxy/managers/collections_util.py | 8 +-
lib/galaxy/managers/datasets.py | 9 +
lib/galaxy/managers/hdas.py | 564 +++++++++++-------
lib/galaxy/managers/histories.py | 60 +-
lib/galaxy/model/__init__.py | 24 +-
lib/galaxy/web/base/controller.py | 93 ---
lib/galaxy/webapps/galaxy/api/datasets.py | 32 +-
.../webapps/galaxy/api/history_contents.py | 251 ++++----
.../webapps/galaxy/controllers/history.py | 11 +-
test/unit/managers/test_DatasetManager.py | 165 ++---
12 files changed, 786 insertions(+), 587 deletions(-)
diff --git a/lib/galaxy/managers/base.py b/lib/galaxy/managers/base.py
index ff4d97aac1b..c1e1d36366b 100644
--- a/lib/galaxy/managers/base.py
+++ b/lib/galaxy/managers/base.py
@@ -401,7 +401,8 @@ class ModelSerializer( object ):
def serialize( self, trans, item, keys ):
# main interface fn for converting a model to a dict
- #TODO: try/catch here?
+#TODO: try/catch here? add option 'fail_fast'/'raise' to allow optionally *catching* the error without raising it
+# (and then returning the error message in the serialized data)
returned = {}
for key in keys:
# check both serializers and serializable keys
@@ -425,9 +426,25 @@ class ModelSerializer( object ):
id = getattr( item, key )
return trans.security.encode_id( id ) if id is not None else None
+ #TODO: AnnotatableMixin
+ def serialize_annotation( self, trans, item, key ):
+#TODO: which is correct here?
+ #user = item.user
+ user = trans.user
+ return item.get_item_annotation_str( trans.sa_session, user, item )
+
+ #TODO: TaggableMixin
+ def serialize_tags( self, trans, item, key ):
+#TODO: whose tags are these?
+ return [ tag.user_tname + ( ':' + tag.user_value if tag.user_value else '' ) for tag in item.tags ]
+
# ......................................................................... serializing to a view
# where a view is a predefied list of keys to serialize
def serialize_to_view( self, trans, item, view=None, keys=None ):
+#TODO: if keys and view - munge view and keys
+# : if keys but no view - use keys
+# : if no keys but view - use view
+# : if no keys and no view - use default view
keys = keys or []
all_keys = self.view_to_list( view ) + keys
return self.serialize( trans, item, all_keys )
@@ -439,6 +456,20 @@ class ModelSerializer( object ):
raise ModelSerializingError( 'unknown view', view=view, available_views=self.views )
return self.views[ view ]
+ #TODO: this is more util/gen. use
+ def pluck_from_list( self, l, elems ):
+ """
+ Removes found elems from list l and returns list of found elems if found.
+ """
+ found = []
+ for elem in elems:
+ try:
+ index = l.index( elem )
+ found.append( l.pop( index ) )
+ except ValueError, val_err:
+ pass
+ return found
+
# ......................................................................... aliases
#def to_dict( self, trans, item, view=None, view_keys=None ):
# view_keys = view_keys or self.view_to_list( view )
@@ -493,6 +524,7 @@ class ModelValidator( object ):
return val
#def slug( self, trans, item, key, val ):
+ # """validate slug"""
# pass
diff --git a/lib/galaxy/managers/collections.py b/lib/galaxy/managers/collections.py
index f474f33d4ea..428f7652614 100644
--- a/lib/galaxy/managers/collections.py
+++ b/lib/galaxy/managers/collections.py
@@ -7,6 +7,7 @@ from galaxy import model
from galaxy.exceptions import MessageException
from galaxy.exceptions import ItemAccessibilityException
from galaxy.exceptions import RequestParameterInvalidException
+from galaxy.managers import base
from galaxy.managers import hdas # TODO: Refactor all mixin use into managers.
from galaxy.managers import histories
from galaxy.managers import lddas
@@ -244,7 +245,7 @@ class DatasetCollectionManager( object ):
if src_type == 'hda':
decoded_id = int( trans.app.security.decode_id( encoded_id ) )
- element = self.hda_manager.get( trans, decoded_id, check_ownership=False )
+ element = self.hda_manager.accessible_by_id( trans, decoded_id, trans.user )
elif src_type == 'ldda':
element = self.ldda_manager.get( trans, encoded_id )
elif src_type == 'hdca':
@@ -293,3 +294,122 @@ class DatasetCollectionManager( object ):
if not trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), collection_instance, trans.user ):
raise ItemAccessibilityException( "LibraryDatasetCollectionAssociation is not accessible to the current user", type='error' )
return collection_instance
+
+
+class DatasetCollectionSerializer( base.ModelSerializer ):
+
+ def __init__( self, app ):
+ super( HDASerializer, self ).__init__( app )
+
+ # # most of these views build/add to the previous view
+ # summary_view = [
+ # 'id', 'history_id', 'hid', 'name', 'dataset_id',
+ # 'state', 'deleted', 'purged', 'visible'
+ # ]
+ # detailed_view = summary_view + [
+ # 'dbkey', 'info', 'blurb', 'extension', 'create_time', 'update_time',
+ # 'copied_from_history_dataset_association_id', 'copied_from_library_dataset_dataset_association_id',
+ # 'metadata', 'size',
+ # #mixin: annotatable
+ # 'annotation',
+ # #mixin: taggable
+ # 'tags',
+ # ]
+ # extended_view = detailed_view + [
+ # #NOTE: sending raw 'peek' (not get_display_peek)
+ # 'peek',
+ # 'tool_version', 'parent_id', 'designation',
+ # ]
+ #
+ # self.serializable_keys = extended_view + [
+ # 'display_apps',
+ # 'display_types',
+ # 'visualizations'
+ # ]
+ # self.views = {
+ # 'summary' : summary_view,
+ # 'detailed' : detailed_view,
+ # 'extended' : extended_view,
+ # }
+ # self.default_view = 'summary'
+ #
+ #def add_serializers( self ):
+ # self.serializers.update({
+ # 'id' : self.serialize_id,
+ # 'history_id' : self.serialize_id,
+ # 'dataset_id' : self.serialize_id,
+ #
+ # 'create_time' : self.serialize_date,
+ # 'update_time' : self.serialize_date,
+ # 'copied_from_history_dataset_association_id' : self.serialize_id,
+ # 'copied_from_library_dataset_dataset_association_id': self.serialize_id,
+ #
+ # 'metadata' : self.serialize_metadata,
+ # 'size' : lambda t, i, k: int( i.get_size() ),
+ # 'nice_size' : lambda t, i, k: i.get_size( nice_size=True ),
+ #
+ # 'parent_id' : self.serialize_id,
+ # #TODO: to mixin: annotatable
+ # 'annotation' : lambda t, i, k: i.get_item_annotation_str( t.sa_session, t.user, i ),
+ # #TODO: to mixin: taggable
+ # 'tags' : lambda t, i, k: [ tag.user_tname + ( ':' + tag.user_value if tag.user_value else '' )
+ # for tag in i.tags ],
+ # })
+
+#def dictify_dataset_collection_instance( dataset_collection_instance, parent, security, view="element" ):
+# dict_value = dataset_collection_instance.to_dict( view=view )
+# encoded_id = security.encode_id( dataset_collection_instance.id )
+# if isinstance( parent, model.History ):
+# encoded_history_id = security.encode_id( parent.id )
+# dict_value[ 'url' ] = web.url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset_collection" )
+# elif isinstance( parent, model.LibraryFolder ):
+# encoded_library_id = security.encode_id( parent.library.id )
+# encoded_folder_id = security.encode_id( parent.id )
+# # TODO: Work in progress - this end-point is not right yet...
+# dict_value[ 'url' ] = web.url_for( 'library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id )
+# if view == "element":
+# dict_value[ 'elements' ] = map( dictify_element, dataset_collection_instance.collection.elements )
+# security.encode_all_ids( dict_value, recursive=True ) # TODO: Use Kyle's recusrive formulation of this.
+# return dict_value
+#
+#
+#def dictify_element( element ):
+# dictified = element.to_dict( view="element" )
+# object_detials = element.element_object.to_dict()
+# if element.child_collection:
+# # Recursively yield elements for each nested collection...
+# object_detials[ "elements" ] = map( dictify_element, element.child_collection.elements )
+#
+# dictified[ "object" ] = object_detials
+# return dictified
+#
+# def serialize_metadata( self, trans, hda, key ):
+# not_included = [ 'dbkey' ]
+# md = {}
+# for name, spec in hda.metadata.spec.items():
+# if name in not_included:
+# continue
+# val = hda.metadata.get( name )
+# #NOTE: no files
+# if isinstance( val, model.MetadataFile ):
+# continue
+# #TODO:? possibly split this off?
+# # If no value for metadata, look in datatype for metadata.
+# elif val is None and hasattr( hda.datatype, name ):
+# val = getattr( hda.datatype, name )
+# md[ name ] = val
+#
+# return md
+
+ #def add_deserializers( self ):
+ # self.deserializers.update({
+ # 'name' : self.deserialize_basestring,
+ # 'genome_build' : self.deserialize_genome_build,
+ # 'deleted' : self.deserialize_bool,
+ # 'published' : self.deserialize_bool,
+ # #TODO: to mixin: annotatable
+ # #'annotation' : self.deserialize_annotation,
+ # #TODO: to mixin: taggable
+ # #'tags' : lambda t, i, k, v: ,
+ # })
+ # self.deserializable_keys = self.deserializers.keys()
diff --git a/lib/galaxy/managers/collections_util.py b/lib/galaxy/managers/collections_util.py
index fd401eb028c..e478f7baa42 100644
--- a/lib/galaxy/managers/collections_util.py
+++ b/lib/galaxy/managers/collections_util.py
@@ -65,9 +65,9 @@ def validate_input_element_identifiers( element_identifiers ):
validate_input_element_identifiers( element_identifier[ "element_identifiers" ] )
-def dictify_dataset_collection_instance( dataset_colleciton_instance, parent, security, view="element" ):
- dict_value = dataset_colleciton_instance.to_dict( view=view )
- encoded_id = security.encode_id( dataset_colleciton_instance.id )
+def dictify_dataset_collection_instance( dataset_collection_instance, parent, security, view="element" ):
+ dict_value = dataset_collection_instance.to_dict( view=view )
+ encoded_id = security.encode_id( dataset_collection_instance.id )
if isinstance( parent, model.History ):
encoded_history_id = security.encode_id( parent.id )
dict_value[ 'url' ] = web.url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset_collection" )
@@ -77,7 +77,7 @@ def dictify_dataset_collection_instance( dataset_colleciton_instance, parent, se
# TODO: Work in progress - this end-point is not right yet...
dict_value[ 'url' ] = web.url_for( 'library_content', library_id=encoded_library_id, id=encoded_id, folder_id=encoded_folder_id )
if view == "element":
- dict_value[ 'elements' ] = map( dictify_element, dataset_colleciton_instance.collection.elements )
+ dict_value[ 'elements' ] = map( dictify_element, dataset_collection_instance.collection.elements )
security.encode_all_ids( dict_value, recursive=True ) # TODO: Use Kyle's recusrive formulation of this.
return dict_value
diff --git a/lib/galaxy/managers/datasets.py b/lib/galaxy/managers/datasets.py
index 84805c633b6..4ac9baf815d 100644
--- a/lib/galaxy/managers/datasets.py
+++ b/lib/galaxy/managers/datasets.py
@@ -92,6 +92,9 @@ class DatasetManager( base.ModelManager, base.AccessibleModelInterface, base.Pur
# =============================================================================
class DatasetAssociationManager( base.ModelManager, base.AccessibleModelInterface, base.PurgableModelInterface ):
+ # A dataset association HAS a dataset but contains metadata specific to a library (lda) or user (hda)
+ model_class = model.DatasetInstance
+ default_order_by = ( model.Dataset.create_time, )
def __init__( self, app ):
super( DatasetAssociationManager, self ).__init__( app )
@@ -100,3 +103,9 @@ class DatasetAssociationManager( base.ModelManager, base.AccessibleModelInterfac
def is_accessible( self, trans, dataset_assoc, user ):
# defer to the dataset
return self.dataset_mgr.is_accessible( trans, dataset_assoc.dataset, user )
+
+ def metadata( self, trans, dataset_assoc ):
+ pass
+
+ def is_being_used( self, trans, dataset_assoc ):
+ pass
diff --git a/lib/galaxy/managers/hdas.py b/lib/galaxy/managers/hdas.py
index 9a1db0cbe68..07567eea128 100644
--- a/lib/galaxy/managers/hdas.py
+++ b/lib/galaxy/managers/hdas.py
@@ -4,6 +4,8 @@ Manager and Serializer for HDAs.
HistoryDatasetAssociations (HDAs) are datasets contained or created in a
history.
"""
+import gettext
+
from galaxy import model
from galaxy import exceptions
@@ -111,93 +113,64 @@ class HDAManager( datasets.DatasetAssociationManager ):
# ......................................................................... serialization
#TODO: move into serializer below
- def get_hda_dict( self, trans, hda ):
- """
- Return full details of this HDA in dictionary form.
- """
- #precondition: the user's access to this hda has already been checked
- #TODO:?? postcondition: all ids are encoded (is this really what we want at this level?)
- expose_dataset_path = trans.user_is_admin() or trans.app.config.expose_dataset_path
- hda_dict = hda.to_dict( view='element', expose_dataset_path=expose_dataset_path )
- hda_dict[ 'api_type' ] = "file"
-
- # Add additional attributes that depend on trans must be added here rather than at the model level.
- can_access_hda = trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset )
- can_access_hda = ( trans.user_is_admin() or can_access_hda )
- if not can_access_hda:
- return self.get_inaccessible_hda_dict( trans, hda )
- hda_dict[ 'accessible' ] = True
-
- #TODO: I'm unclear as to which access pattern is right
- hda_dict[ 'annotation' ] = hda.get_item_annotation_str( trans.sa_session, hda.history.user, hda )
- #annotation = getattr( hda, 'annotation', hda.get_item_annotation_str( trans.sa_session, trans.user, hda ) )
-
- # ---- return here if deleted AND purged OR can't access
- purged = ( hda.purged or hda.dataset.purged )
- if ( hda.deleted and purged ):
- #TODO: to_dict should really go AFTER this - only summary data
- return trans.security.encode_dict_ids( hda_dict )
-
- if expose_dataset_path:
- try:
- hda_dict[ 'file_name' ] = hda.file_name
- except objectstore.ObjectNotFound:
- log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
-
- hda_dict[ 'download_url' ] = galaxy.web.url_for( 'history_contents_display',
- history_id = trans.security.encode_id( hda.history.id ),
- history_content_id = trans.security.encode_id( hda.id ) )
-
- # indeces, assoc. metadata files, etc.
- meta_files = []
- for meta_type in hda.metadata.spec.keys():
- if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
- meta_files.append( dict( file_type=meta_type ) )
- if meta_files:
- hda_dict[ 'meta_files' ] = meta_files
-
- # currently, the viz reg is optional - handle on/off
- if trans.app.visualizations_registry:
- hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
- else:
- hda_dict[ 'visualizations' ] = hda.get_visualizations()
- #TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
- # when the visualizations button is clicked (instead of preloading/pre-checking)
-
- # ---- return here if deleted
- if hda.deleted and not purged:
- return trans.security.encode_dict_ids( hda_dict )
-
- return trans.security.encode_dict_ids( hda_dict )
-
- def get_inaccessible_hda_dict( self, trans, hda ):
- """
- Return truncated serialization of HDA when inaccessible to user.
- """
- return trans.security.encode_dict_ids({
- 'id' : hda.id,
- 'history_id': hda.history.id,
- 'hid' : hda.hid,
- 'name' : hda.name,
- 'state' : hda.state,
- 'deleted' : hda.deleted,
- 'visible' : hda.visible,
- 'accessible': False
- })
-
- def get_hda_dict_with_error( self, trans, hda=None, history_id=None, id=None, error_msg='Error' ):
- """
- Return truncated serialization of HDA when error raised getting
- details.
- """
- return trans.security.encode_dict_ids({
- 'id' : hda.id if hda else id,
- 'history_id': hda.history.id if hda else history_id,
- 'hid' : hda.hid if hda else '(unknown)',
- 'name' : hda.name if hda else '(unknown)',
- 'error' : error_msg,
- 'state' : trans.model.Dataset.states.NEW
- })
+ #def get_hda_dict( self, trans, hda ):
+ # """
+ # Return full details of this HDA in dictionary form.
+ # """
+ # #precondition: the user's access to this hda has already been checked
+ # #TODO:?? postcondition: all ids are encoded (is this really what we want at this level?)
+ # expose_dataset_path = trans.user_is_admin() or trans.app.config.expose_dataset_path
+ # hda_dict = hda.to_dict( view='element', expose_dataset_path=expose_dataset_path )
+ # hda_dict[ 'api_type' ] = "file"
+ #
+ # # Add additional attributes that depend on trans must be added here rather than at the model level.
+ # can_access_hda = trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset )
+ # can_access_hda = ( trans.user_is_admin() or can_access_hda )
+ # if not can_access_hda:
+ # return self.get_inaccessible_hda_dict( trans, hda )
+ # hda_dict[ 'accessible' ] = True
+ #
+ # #TODO: I'm unclear as to which access pattern is right
+ # hda_dict[ 'annotation' ] = hda.get_item_annotation_str( trans.sa_session, hda.history.user, hda )
+ # #annotation = getattr( hda, 'annotation', hda.get_item_annotation_str( trans.sa_session, trans.user, hda ) )
+ #
+ # # ---- return here if deleted AND purged OR can't access
+ # purged = ( hda.purged or hda.dataset.purged )
+ # if ( hda.deleted and purged ):
+ # #TODO: to_dict should really go AFTER this - only summary data
+ # return trans.security.encode_dict_ids( hda_dict )
+ #
+ # if expose_dataset_path:
+ # try:
+ # hda_dict[ 'file_name' ] = hda.file_name
+ # except objectstore.ObjectNotFound:
+ # log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
+ #
+ # hda_dict[ 'download_url' ] = galaxy.web.url_for( 'history_contents_display',
+ # history_id = trans.security.encode_id( hda.history.id ),
+ # history_content_id = trans.security.encode_id( hda.id ) )
+ #
+ # # indeces, assoc. metadata files, etc.
+ # meta_files = []
+ # for meta_type in hda.metadata.spec.keys():
+ # if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
+ # meta_files.append( dict( file_type=meta_type ) )
+ # if meta_files:
+ # hda_dict[ 'meta_files' ] = meta_files
+ #
+ # # currently, the viz reg is optional - handle on/off
+ # if trans.app.visualizations_registry:
+ # hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
+ # else:
+ # hda_dict[ 'visualizations' ] = hda.get_visualizations()
+ # #TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
+ # # when the visualizations button is clicked (instead of preloading/pre-checking)
+ #
+ # # ---- return here if deleted
+ # if hda.deleted and not purged:
+ # return trans.security.encode_dict_ids( hda_dict )
+ #
+ # return trans.security.encode_dict_ids( hda_dict )
def get_display_apps( self, trans, hda ):
"""
@@ -211,7 +184,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
app_links.append({
'target': link_app.url.get( 'target_frame', '_blank' ),
'href' : link_app.get_display_url( hda, trans ),
- 'text' : gettext( link_app.name )
+ 'text' : gettext.gettext( link_app.name )
})
if app_links:
display_apps.append( dict( label=display_app.name, links=app_links ) )
@@ -238,13 +211,19 @@ class HDAManager( datasets.DatasetAssociationManager ):
app_links.append({
'target': target_frame,
'href' : display_link,
- 'text' : gettext( display_name )
+ 'text' : gettext.gettext( display_name )
})
if app_links:
display_apps.append( dict( label=display_label, links=app_links ) )
return display_apps
+ def get_visualizations( self, trans, hda ):
+ # use older system if registry is off in the config
+ if not trans.app.visualizations_registry:
+ return hda.get_visualizations()
+ return trans.app.visualizations_registry.get_visualizations( trans, hda )
+
#def get_dataset( self, trans, dataset_id, check_ownership=True, check_accessible=False, check_state=True ):
# """
# Get an HDA object by id performing security checks using
@@ -378,67 +357,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
# if dataset.state != trans.app.model.Job.states.OK:
# return dataset.conversion_messages.PENDING
# return None
- #
- #def get_hda_dict( self, trans, hda ):
- # """Return full details of this HDA in dictionary form.
- # """
- # #precondition: the user's access to this hda has already been checked
- # #TODO:?? postcondition: all ids are encoded (is this really what we want at this level?)
- # expose_dataset_path = trans.user_is_admin() or trans.app.config.expose_dataset_path
- # hda_dict = hda.to_dict( view='element', expose_dataset_path=expose_dataset_path )
- # hda_dict[ 'api_type' ] = "file"
- #
- # # Add additional attributes that depend on trans can hence must be added here rather than at the model level.
- # can_access_hda = trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset )
- # can_access_hda = ( trans.user_is_admin() or can_access_hda )
- # if not can_access_hda:
- # return self.get_inaccessible_hda_dict( trans, hda )
- # hda_dict[ 'accessible' ] = True
- #
- # #TODO: I'm unclear as to which access pattern is right
- # hda_dict[ 'annotation' ] = hda.get_item_annotation_str( trans.sa_session, trans.user, hda )
- # #annotation = getattr( hda, 'annotation', hda.get_item_annotation_str( trans.sa_session, trans.user, hda ) )
- #
- # # ---- return here if deleted AND purged OR can't access
- # purged = ( hda.purged or hda.dataset.purged )
- # if hda.deleted and purged:
- # #TODO: to_dict should really go AFTER this - only summary data
- # return trans.security.encode_dict_ids( hda_dict )
- #
- # if expose_dataset_path:
- # try:
- # hda_dict[ 'file_name' ] = hda.file_name
- # except objectstore.ObjectNotFound:
- # log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
- #
- # hda_dict[ 'download_url' ] = url_for( 'history_contents_display',
- # history_id = trans.security.encode_id( hda.history.id ),
- # history_content_id = trans.security.encode_id( hda.id ) )
- #
- # # resubmitted is not a real state
- # hda_dict[ 'resubmitted' ] = False
- # if hda.state == trans.app.model.Dataset.states.RESUBMITTED:
- # hda_dict[ 'state' ] = hda.dataset.state
- # hda_dict[ 'resubmitted' ] = True
- #
- # # indeces, assoc. metadata files, etc.
- # meta_files = []
- # for meta_type in hda.metadata.spec.keys():
- # if isinstance( hda.metadata.spec[ meta_type ].param, FileParameter ):
- # meta_files.append( dict( file_type=meta_type ) )
- # if meta_files:
- # hda_dict[ 'meta_files' ] = meta_files
- #
- # # currently, the viz reg is optional - handle on/off
- # if trans.app.visualizations_registry:
- # hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
- # else:
- # hda_dict[ 'visualizations' ] = hda.get_visualizations()
- # #TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
- # # when the visualizations button is clicked (instead of preloading/pre-checking)
- #
- # return trans.security.encode_dict_ids( hda_dict )
- #
+
#def get_inaccessible_hda_dict( self, trans, hda ):
# return trans.security.encode_dict_ids({
# 'id' : hda.id,
@@ -450,7 +369,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
# 'visible' : hda.visible,
# 'accessible': False
# })
- #
+
#def get_hda_dict_with_error( self, trans, hda=None, history_id=None, id=None, error_msg='Error' ):
# return trans.security.encode_dict_ids({
# 'id' : hda.id if hda else id,
@@ -460,47 +379,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
# 'error' : error_msg,
# 'state' : trans.model.Dataset.states.NEW
# })
- #
- #def get_display_apps( self, trans, hda ):
- # display_apps = []
- # for display_app in hda.get_display_applications( trans ).itervalues():
- #
- # app_links = []
- # for link_app in display_app.links.itervalues():
- # app_links.append({
- # 'target': link_app.url.get( 'target_frame', '_blank' ),
- # 'href' : link_app.get_display_url( hda, trans ),
- # 'text' : gettext( link_app.name )
- # })
- # if app_links:
- # display_apps.append( dict( label=display_app.name, links=app_links ) )
- #
- # return display_apps
- #
- #def get_old_display_applications( self, trans, hda ):
- # display_apps = []
- # if not trans.app.config.enable_old_display_applications:
- # return display_apps
- #
- # for display_app in hda.datatype.get_display_types():
- # target_frame, display_links = hda.datatype.get_display_links( hda,
- # display_app, trans.app, trans.request.base )
- #
- # if len( display_links ) > 0:
- # display_label = hda.datatype.get_display_label( display_app )
- #
- # app_links = []
- # for display_name, display_link in display_links:
- # app_links.append({
- # 'target': target_frame,
- # 'href' : display_link,
- # 'text' : gettext( display_name )
- # })
- # if app_links:
- # display_apps.append( dict( label=display_label, links=app_links ) )
- #
- # return display_apps
- #
+
#def set_hda_from_dict( self, trans, hda, new_data ):
# """
# Changes HDA data using the given dictionary new_data.
@@ -522,7 +401,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
# trans.sa_session.flush()
#
# return changed
- #
+
#def get_hda_job( self, hda ):
# # Get dataset's job.
# job = None
@@ -530,7 +409,7 @@ class HDAManager( datasets.DatasetAssociationManager ):
# job = job_output_assoc.job
# break
# return job
- #
+
#def stop_hda_creating_job( self, hda ):
# """
# Stops an HDA's creating job if all the job's other outputs are deleted.
@@ -543,39 +422,138 @@ class HDAManager( datasets.DatasetAssociationManager ):
# if job.check_if_output_datasets_deleted():
# job.mark_deleted( self.app.config.track_jobs_in_database )
# self.app.job_manager.job_stop_queue.put( job.id )
+
+# old file
+ #def get( self, trans, unencoded_id, check_ownership=True, check_accessible=True ):
+ # """
+ # Get an HDA by its unencoded db id, checking ownership (via its history)
+ # or accessibility (via dataset shares/permissions).
+ # """
+ # hda = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( unencoded_id )
+ # if hda is None:
+ # raise exceptions.ObjectNotFound()
+ # hda = self.secure( trans, hda, check_ownership, check_accessible )
+ # return hda
#
+ #def secure( self, trans, hda, check_ownership=True, check_accessible=True ):
+ # """
+ # check ownership (via its history) or accessibility (via dataset
+ # shares/permissions).
+ # """
+ # # all items are accessible to an admin
+ # if trans.user and trans.user_is_admin():
+ # return hda
+ # if check_ownership:
+ # hda = self.check_ownership( trans, hda )
+ # if check_accessible:
+ # hda = self.check_accessible( trans, hda )
+ # return hda
#
+ #def can_access_dataset( self, trans, hda ):
+ # """
+ # Use security agent to see if current user has access to dataset.
+ # """
+ # current_user_roles = trans.get_current_user_roles()
+ # return trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset )
+ #
+ ##TODO: is_owner, is_accessible
+ #
+ #def check_ownership( self, trans, hda ):
+ # """
+ # Use history to see if current user owns HDA.
+ # """
+ # if not trans.user:
+ # #if hda.history == trans.history:
+ # # return hda
+ # raise exceptions.AuthenticationRequired( "Must be logged in to manage Galaxy datasets", type='error' )
+ # if trans.user_is_admin():
+ # return hda
+ # # check for ownership of the containing history and accessibility of the underlying dataset
+ # if( self.histories_mgr.is_owner( trans, hda.history )
+ # and self.can_access_dataset( trans, hda ) ):
+ # return hda
+ # raise exceptions.ItemOwnershipException(
+ # "HistoryDatasetAssociation is not owned by the current user", type='error' )
+
+ #def check_accessible( self, trans, hda ):
+ # """
+ # Raise error if HDA is not accessible.
+ # """
+ # if trans.user and trans.user_is_admin():
+ # return hda
+ # # check for access of the containing history...
+ # self.histories_mgr.check_accessible( trans, hda.history )
+ # # ...then the underlying dataset
+ # if self.can_access_dataset( trans, hda ):
+ # return hda
+ # raise exceptions.ItemAccessibilityException(
+ # "HistoryDatasetAssociation is not accessible to the current user", type='error' )
+
# =============================================================================
class HDASerializer( base.ModelSerializer ):
+ #TODO: inherit from datasets.DatasetAssociationSerializer
- def __init__( self ):
- super( HDASerializer, self ).__init__()
+ def __init__( self, app ):
+ super( HDASerializer, self ).__init__( app )
+ self.hda_mgr = HDAManager( app )
# most of these views build/add to the previous view
summary_view = [
- 'id', 'history_id', 'hid', 'name', 'dataset_id',
- 'state', 'deleted', 'purged', 'visible'
+ 'id', 'name',
+ 'history_id', 'hid', 'history_content_type',
+ #'dataset_id',
+ 'state', 'extension',
+ 'deleted', 'purged', 'visible', 'resubmitted',
+ 'type', 'url'
]
- detailed_view = summary_view + [
- 'dbkey', 'info', 'blurb', 'extension', 'create_time', 'update_time',
- 'copied_from_history_dataset_association_id', 'copied_from_library_dataset_dataset_association_id',
- 'metadata', 'size',
+ inaccessible = [
+ 'id', 'name', 'history_id', 'hid', 'history_content_type',
+ 'state', 'deleted', 'visible'
+ ]
+ detailed_view = [
+ 'id', 'name', 'model_class',
+ 'history_id', 'hid',
+ # why include if model_class is there?
+ 'hda_ldda', 'history_content_type',
+ 'state',
+ 'accessible',
+ 'deleted', 'purged', 'visible', 'resubmitted',
+
+ # remapped
+ 'genome_build', 'misc_info', 'misc_blurb',
+ 'file_ext', 'file_size',
+
+ 'create_time', 'update_time',
+ 'metadata', 'meta_files', 'data_type',
+ 'peek',
+
+ #TODO: why is this named uuid!? The hda doesn't have a uuid - it's the underlying dataset's uuid!
+ 'uuid',
+ # should be:
+ #'dataset_uuid',
+
+ 'file_path',
+ 'display_apps',
+ 'display_types',
+ 'visualizations',
+
+ #'url',
+ 'download_url',
+
#mixin: annotatable
'annotation',
#mixin: taggable
'tags',
+
+ 'api_type'
]
extended_view = detailed_view + [
#NOTE: sending raw 'peek' (not get_display_peek)
- 'peek',
'tool_version', 'parent_id', 'designation',
]
self.serializable_keys = extended_view + [
- 'display_apps',
- 'display_types',
- 'visualizations'
]
self.views = {
'summary' : summary_view,
@@ -586,52 +564,190 @@ class HDASerializer( base.ModelSerializer ):
def add_serializers( self ):
self.serializers.update({
+ 'model_class' : lambda *a: 'HistoryDatasetAssociation',
+ 'accessible' : lambda *a: True,
+
'id' : self.serialize_id,
'history_id' : self.serialize_id,
+ 'history_content_type': lambda *a: 'dataset',
'dataset_id' : self.serialize_id,
+ 'hda_ldda' : lambda *a: 'hda',
+
+ # remapped
+ 'info' : lambda t, i, k: i.info.strip() if isinstance( i.info, basestring ) else i.info,
+ 'misc_info' : lambda t, i, k: self.serializers[ 'info' ]( t, i, k ),
+ 'misc_blurb' : lambda t, i, k: i.blurb,
+ 'genome_build' : lambda t, i, k: i.dbkey,
+ 'file_ext' : lambda t, i, k: i.extension,
+ 'file_size' : lambda t, i, k: self.serializers[ 'size' ]( t, i, k ),
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'copied_from_history_dataset_association_id' : self.serialize_id,
'copied_from_library_dataset_dataset_association_id': self.serialize_id,
- 'metadata' : self.serialize_metadata,
+
+ 'resubmitted' : lambda t, i, k: i._state == t.app.model.Dataset.states.RESUBMITTED,
+
+ 'meta_files' : self.serialize_meta_files,
+ 'file_path' : self.serialize_file_path,
+
'size' : lambda t, i, k: int( i.get_size() ),
'nice_size' : lambda t, i, k: i.get_size( nice_size=True ),
+ 'data_type' : lambda t, i, k: i.datatype.__class__.__module__ + '.' + i.datatype.__class__.__name__,
+ 'peek' : lambda t, i, k: i.display_peek() if i.peek and i.peek != 'no peek' else None,
+
+ # currently we send this sub-obj flattened (see serialize and add_flattened_metadata below)
+ #'metadata' : self.serialize_metadata,
+ # make it available here under a different key
+ 'metadata_dict' : self.serialize_metadata,
+
'parent_id' : self.serialize_id,
- #TODO: to mixin: annotatable
- 'annotation' : lambda t, i, k: i.get_item_annotation_str( t.sa_session, t.user, i ),
- #TODO: to mixin: taggable
- 'tags' : lambda t, i, k: [ tag.user_tname + ( ':' + tag.user_value if tag.user_value else '' )
- for tag in i.tags ],
+ 'annotation' : self.serialize_annotation,
+ 'tags' : self.serialize_tags,
+
+ 'display_apps' : lambda t, i, k: self.hda_mgr.get_display_apps( t, i ),
+ 'display_types' : lambda t, i, k: self.hda_mgr.get_old_display_applications( t, i ),
+ 'visualizations': lambda t, i, k: self.hda_mgr.get_visualizations( t, i ),
+
+ 'dataset_uuid' : lambda t, i, k: str( i.dataset.uuid ) if i.dataset.uuid else None,
+ 'uuid' : lambda t, i, k: self.serializers[ 'dataset_uuid' ]( t, i, k ),
+
+ #'url' : url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset" ),
+ #TODO: this intermittently causes a routes.GenerationException - temp use the legacy route to prevent this
+ # see also: https://trello.com/c/5d6j4X5y
+ # see also: https://sentry.galaxyproject.org/galaxy/galaxy-main/group/20769/events/9352883/
+ 'url' : lambda t, i, k: galaxy.web.url_for( 'history_content',
+ history_id=t.security.encode_id( i.history_id ), id=t.security.encode_id( i.id ) ),
+
+ 'urls' : self.serialize_urls,
+ 'download_url' : lambda t, i, k: galaxy.web.url_for( 'history_contents_display',
+ history_id=t.security.encode_id( i.history.id ),
+ history_content_id=t.security.encode_id( i.id ) ),
+
+ 'api_type' : lambda *a: 'file',
+ 'type' : lambda *a: 'file'
})
- def serialize_metadata( self, trans, hda, key ):
- not_included = [ 'dbkey' ]
- md = {}
+ def serialize( self, trans, hda, keys ):
+ # if 'metadata' isn't removed from keys here serialize will retrieve the un-serializable MetadataCollection
+ #TODO: remove these when metadata is sub-object
+ KEYS_HANDLED_SEPARATELY = ( 'metadata', )
+ left_to_handle = self.pluck_from_list( keys, KEYS_HANDLED_SEPARATELY )
+ serialized = super( HDASerializer, self ).serialize( trans, hda, keys )
+
+ if 'metadata' in left_to_handle:
+ # we currently add metadata directly to the dict instead of as a sub-object
+ serialized.update( self.prefixed_metadata( trans, hda ) )
+ return serialized
+
+ def prefixed_metadata( self, trans, hda ):
+ """
+ Adds (a prefixed version of) the hda metadata to the dict, prefixing each key
+ with 'metadata_'.
+ """
+ metadata = self.serialize_metadata( trans, hda, 'metadata' )
+ #TODO: this is factored out for removal - metadata should be a sub-object instead:
+ # i.e. 'metadata' : self.serialize_metadata,
+ prefixed = {}
+ for key, val in metadata.items():
+ prefixed_key = 'metadata_' + key
+ prefixed[ prefixed_key ] = val
+ return prefixed
+
+ def serialize_metadata( self, trans, hda, key, excluded=None ):
+ # dbkey is a repeat actually (metadata_dbkey == genome_build)
+ #excluded = [ 'dbkey' ] if excluded is None else excluded
+ excluded = [] if excluded is None else excluded
+
+ metadata = {}
for name, spec in hda.metadata.spec.items():
- if name in not_included:
+ if name in excluded:
continue
val = hda.metadata.get( name )
#NOTE: no files
if isinstance( val, model.MetadataFile ):
- continue
+ # only when explicitly set: fetching filepaths can be expensive
+ if not trans.app.config.expose_dataset_path:
+ continue
+ val = val.file_name
#TODO:? possibly split this off?
# If no value for metadata, look in datatype for metadata.
elif val is None and hasattr( hda.datatype, name ):
val = getattr( hda.datatype, name )
- md[ name ] = val
+ metadata[ name ] = val
- return md
+ return metadata
+ # add to serialize_metadata above
+ def serialize_meta_files( self, trans, hda, key ):
+ meta_files = []
+ for meta_type in hda.metadata.spec.keys():
+ if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
+ meta_files.append( dict( file_type=meta_type ) )
+ return meta_files
+
+ #def file_info #TODO
+ #TODO: and to dataset instead (passing through object store)
+ def serialize_file_path( self, trans, hda, key ):
+#TODO: allow admin
+ if trans.app.config.expose_dataset_path:
+ try:
+ return hda.file_name
+ except objectstore.ObjectNotFound:
+ log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
+ return None
+
+ def serialize_urls( self, trans, hda, key ):
+ url_for = galaxy.web.url_for
+ encoded_id = trans.security.encode_id( hda.id )
+ urls = {
+ 'purge' : url_for( controller='dataset', action='purge_async', dataset_id=encoded_id ),
+ 'display' : url_for( controller='dataset', action='display', dataset_id=encoded_id, preview=True ),
+ 'edit' : url_for( controller='dataset', action='edit', dataset_id=encoded_id ),
+ 'download' : url_for( controller='dataset', action='display',
+ dataset_id=encoded_id, to_ext=hda.extension ),
+ 'report_error' : url_for( controller='dataset', action='errors', id=encoded_id ),
+ 'rerun' : url_for( controller='tool_runner', action='rerun', id=encoded_id ),
+ 'show_params' : url_for( controller='dataset', action='show_params', dataset_id=encoded_id ),
+ 'visualization' : url_for( controller='visualization', action='index',
+ id=encoded_id, model='HistoryDatasetAssociation' ),
+ 'meta_download' : url_for( controller='dataset', action='get_metadata_file',
+ hda_id=encoded_id, metadata_name='' ),
+ }
+ return urls
+
+
+# =============================================================================
+class HDADeserializer( base.ModelDeserializer ):
+ """
+ Interface/service object for validating and deserializing dictionaries into histories.
+ """
+
+ def __init__( self, app ):
+ super( HDADeserializer, self ).__init__( app )
+ self.hda_mgr = HDAManager( app )
+
+ #assumes: incoming from json.loads and sanitized
def add_deserializers( self ):
+ super( HDADeserializer, self ).add_deserializers()
self.deserializers.update({
'name' : self.deserialize_basestring,
- 'genome_build' : self.deserialize_genome_build,
+ 'visible' : self.deserialize_bool,
+
+ # remapped
+ 'genome_build' : lambda t, i, k, v: self.deserialize_genome_build( t, i, 'dbkey', v ),
+ 'misc_info' : lambda t, i, k, v: self.deserialize_basestring( t, i, 'info', v ),
+
+ # mixin: annotatable
+ 'annotation' : self.deserialize_annotation,
+ # mixin: taggable
+ 'tags' : self.deserialize_tags,
+
+ # mixin: deletable
'deleted' : self.deserialize_bool,
+ # sharable
'published' : self.deserialize_bool,
- #TODO: to mixin: annotatable
- #'annotation' : self.deserialize_annotation,
- #TODO: to mixin: taggable
- #'tags' : lambda t, i, k, v: ,
+ 'importable' : self.deserialize_bool,
+
})
self.deserializable_keys = self.deserializers.keys()
diff --git a/lib/galaxy/managers/histories.py b/lib/galaxy/managers/histories.py
index ed361ef068f..883a5fe3741 100644
--- a/lib/galaxy/managers/histories.py
+++ b/lib/galaxy/managers/histories.py
@@ -90,31 +90,28 @@ class HistoryManager( sharable.SharableModelManager, base.PurgableModelInterface
super( HistoryManager, self ).purge( trans, history, flush=flush, **kwargs )
# ......................................................................... serialization
- #TODO: move to serializer
+ #TODO: move to serializer (i.e. history with contents attr)
def _get_history_data( self, trans, history ):
"""
Returns a dictionary containing ``history`` and ``contents``, serialized
history and an array of serialized history contents respectively.
"""
- # import here prevents problems related to circular dependecy between histories and hdas managers.
- import galaxy.managers.hdas
- hda_mgr = galaxy.managers.hdas.HDAManager()
+ #TODO: instantiate here? really?
+ history_serializer = HistorySerializer( self.app )
+ hda_serializer = hdas.HDASerializer( self.app )
collection_dictifier = galaxy.managers.collections_util.dictify_dataset_collection_instance
history_dictionary = {}
contents_dictionaries = []
try:
+ history_dictionary = history_serializer.serialize_to_view( trans, history, view='detailed' )
+
#for content in history.contents_iter( **contents_kwds ):
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
- hda_dict = {}
+ contents_dict = {}
if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
- try:
- hda_dict = hda_mgr.get_hda_dict( trans, content )
- except Exception, exc:
- # don't fail entire list if hda err's, record and move on
- log.exception( 'Error bootstrapping hda: %s', exc )
- hda_dict = hda_mgr.get_hda_dict_with_error( trans, content, str( exc ) )
+ contents_dict = hda_serializer.serialize_to_view( trans, hda, view='detailed' )
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
try:
@@ -124,17 +121,14 @@ class HistoryManager( sharable.SharableModelManager, base.PurgableModelInterface
instance_type='history',
id=trans.security.encode_id( content.id ),
)
- hda_dict = collection_dictifier( dataset_collection_instance,
+ contents_dict = collection_dictifier( dataset_collection_instance,
security=trans.security, parent=dataset_collection_instance.history, view="element" )
except Exception, exc:
log.exception( "Error in history API at listing dataset collection: %s", exc )
#TODO: return some dict with the error
- contents_dictionaries.append( hda_dict )
-
- # re-use the hdas above to get the history data...
- history_dictionary = self.get_history_dict( trans, history, contents_dictionaries=contents_dictionaries )
+ contents_dictionaries.append( contents_dict )
except Exception, exc:
user_id = str( trans.user.id ) if trans.user else '(anonymous)'
@@ -226,6 +220,8 @@ class HistorySerializer( base.ModelSerializer ):
def __init__( self, app ):
super( HistorySerializer, self ).__init__( app )
self.history_mgr = HistoryManager( app )
+ self.hda_mgr = hdas.HDAManager( app )
+ self.hda_serializer = hdas.HDASerializer( app )
summary_view = [
'id',
@@ -274,24 +270,44 @@ class HistorySerializer( base.ModelSerializer ):
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'user_id' : self.serialize_id,
- 'hdas' : lambda t, i, k: [ t.security.encode_id( hda.id ) for hda in i.datasets ],
'size' : lambda t, i, k: int( i.get_disk_size() ),
'nice_size' : lambda t, i, k: i.get_disk_size( nice_size=True ),
- #TODO: to mixins: annotatable, taggable
- 'annotation' : lambda t, i, k: i.get_item_annotation_str( t.sa_session, t.user, i ),
- 'tags' : lambda t, i, k: [ tag.user_tname + ( ':' + tag.user_value if tag.user_value else '' )
- for tag in i.tags ],
+
+ 'annotation' : self.serialize_annotation,
+ 'tags' : self.serialize_tags,
'url' : lambda t, i, k: galaxy.web.url_for( 'history', id=t.security.encode_id( i.id ) ),
'contents_url' : lambda t, i, k:
galaxy.web.url_for( 'history_contents', history_id=t.security.encode_id( i.id ) ),
'empty' : lambda t, i, k: len( i.datasets ) <= 0,
+ 'hdas' : lambda t, i, k: [ t.security.encode_id( hda.id ) for hda in i.datasets ],
'state' : lambda t, i, k: self.history_mgr.get_history_state( t, i ),
'state_details' : lambda t, i, k: self.history_mgr.get_state_counts( t, i ),
'state_ids' : lambda t, i, k: self.history_mgr.get_state_ids( t, i ),
+ 'contents' : self.serialize_contents
})
-## =============================================================================
+ def serialize_contents( self, trans, history, key ):
+ for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
+ contents_dict = {}
+ if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
+ contents_dict = self.hda_serializer.serialize_to_view( trans, hda, view='detailed' )
+ elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
+ contents_dict = self.serialize_collection( trans, collection )
+ contents_dictionaries.append( contents_dict )
+
+ def serialize_collection( self, trans, collection ):
+ service = trans.app.dataset_collections_service
+ dataset_collection_instance = service.get_dataset_collection_instance(
+ trans=trans,
+ instance_type='history',
+ id=trans.security.encode_id( collection.id ),
+ )
+ return collection_dictifier( dataset_collection_instance,
+ security=trans.security, parent=dataset_collection_instance.history, view="element" )
+
+
+# =============================================================================
class HistoryDeserializer( base.ModelDeserializer ):
"""
Interface/service object for validating and deserializing dictionaries into histories.
diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index 451c29aaf23..094ffc9a033 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1111,15 +1111,16 @@ class History( object, Dictifiable, UsesAnnotations, HasName ):
def active_datasets_children_and_roles( self ):
if not hasattr(self, '_active_datasets_children_and_roles'):
db_session = object_session( self )
- query = db_session.query( HistoryDatasetAssociation ).filter( HistoryDatasetAssociation.table.c.history_id == self.id ). \
- filter( not_( HistoryDatasetAssociation.deleted ) ). \
- order_by( HistoryDatasetAssociation.table.c.hid.asc() ). \
- options(
- joinedload("children"),
- joinedload("dataset"),
- joinedload("dataset.actions"),
- joinedload("dataset.actions.role"),
- )
+ query = ( db_session.query( HistoryDatasetAssociation )
+ .filter( HistoryDatasetAssociation.table.c.history_id == self.id )
+ .filter( not_( HistoryDatasetAssociation.deleted ) )
+ .order_by( HistoryDatasetAssociation.table.c.hid.asc() )
+ .options(
+ joinedload("children"),
+ joinedload("dataset"),
+ joinedload("dataset.actions"),
+ joinedload("dataset.actions.role"),
+ ))
self._active_datasets_children_and_roles = query.all()
return self._active_datasets_children_and_roles
@@ -1551,7 +1552,9 @@ class DatasetInstance( object ):
def datatype( self ):
return datatypes_registry.get_datatype_by_extension( self.extension )
def get_metadata( self ):
- if not hasattr( self, '_metadata_collection' ) or self._metadata_collection.parent != self: #using weakref to store parent (to prevent circ ref), does a Session.clear() cause parent to be invalidated, while still copying over this non-database attribute?
+ # using weakref to store parent (to prevent circ ref),
+ # does a Session.clear() cause parent to be invalidated, while still copying over this non-database attribute?
+ if not hasattr( self, '_metadata_collection' ) or self._metadata_collection.parent != self:
self._metadata_collection = MetadataCollection( self )
return self._metadata_collection
def set_metadata( self, bunch ):
@@ -2078,6 +2081,7 @@ class HistoryDatasetAssociation( DatasetInstance, Dictifiable, UsesAnnotations,
rval['extended_metadata'] = hda.extended_metadata.data
rval[ 'peek' ] = to_unicode( hda.display_peek() )
+
for name, spec in hda.metadata.spec.items():
val = hda.metadata.get( name )
if isinstance( val, MetadataFile ):
diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py
index 0dccd69f4e4..dd55877f1bb 100644
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -108,17 +108,6 @@ class BaseController( object ):
"""
return trans.security.encode_all_ids( rval, recursive=recursive )
- # incoming param validation
- # should probably be in sep. serializer class/object _used_ by controller
- def validate_and_sanitize_basestring( self, key, val ):
- return validation.validate_and_sanitize_basestring( key, val )
-
- def validate_and_sanitize_basestring_list( self, key, val ):
- return validation.validate_and_sanitize_basestring_list( key, val )
-
- def validate_boolean( self, key, val ):
- return validation.validate_boolean( key, val )
-
Root = BaseController
@@ -479,88 +468,6 @@ class UsesHistoryDatasetAssociationMixin:
return dataset.conversion_messages.PENDING
return None
- def get_hda_dict( self, trans, hda ):
- """Return full details of this HDA in dictionary form.
- """
- #precondition: the user's access to this hda has already been checked
- #TODO:?? postcondition: all ids are encoded (is this really what we want at this level?)
- expose_dataset_path = trans.user_is_admin() or trans.app.config.expose_dataset_path
- hda_dict = hda.to_dict( view='element', expose_dataset_path=expose_dataset_path )
- hda_dict[ 'api_type' ] = "file"
-
- # Add additional attributes that depend on trans can hence must be added here rather than at the model level.
- can_access_hda = trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset )
- can_access_hda = ( trans.user_is_admin() or can_access_hda )
- if not can_access_hda:
- return self.get_inaccessible_hda_dict( trans, hda )
- hda_dict[ 'accessible' ] = True
-
- #TODO: I'm unclear as to which access pattern is right
- hda_dict[ 'annotation' ] = hda.get_item_annotation_str( trans.sa_session, trans.user, hda )
- #annotation = getattr( hda, 'annotation', hda.get_item_annotation_str( trans.sa_session, trans.user, hda ) )
-
- # ---- return here if deleted AND purged OR can't access
- purged = ( hda.purged or hda.dataset.purged )
- if hda.deleted and purged:
- #TODO: to_dict should really go AFTER this - only summary data
- return trans.security.encode_dict_ids( hda_dict )
-
- if expose_dataset_path:
- try:
- hda_dict[ 'file_name' ] = hda.file_name
- except objectstore.ObjectNotFound:
- log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
-
- hda_dict[ 'download_url' ] = url_for( 'history_contents_display',
- history_id = trans.security.encode_id( hda.history.id ),
- history_content_id = trans.security.encode_id( hda.id ) )
-
- # resubmitted is not a real state
- hda_dict[ 'resubmitted' ] = False
- if hda.state == trans.app.model.Dataset.states.RESUBMITTED:
- hda_dict[ 'state' ] = hda.dataset.state
- hda_dict[ 'resubmitted' ] = True
-
- # indeces, assoc. metadata files, etc.
- meta_files = []
- for meta_type in hda.metadata.spec.keys():
- if isinstance( hda.metadata.spec[ meta_type ].param, FileParameter ):
- meta_files.append( dict( file_type=meta_type ) )
- if meta_files:
- hda_dict[ 'meta_files' ] = meta_files
-
- # currently, the viz reg is optional - handle on/off
- if trans.app.visualizations_registry:
- hda_dict[ 'visualizations' ] = trans.app.visualizations_registry.get_visualizations( trans, hda )
- else:
- hda_dict[ 'visualizations' ] = hda.get_visualizations()
- #TODO: it may also be wiser to remove from here and add as API call that loads the visualizations
- # when the visualizations button is clicked (instead of preloading/pre-checking)
-
- return trans.security.encode_dict_ids( hda_dict )
-
- def get_inaccessible_hda_dict( self, trans, hda ):
- return trans.security.encode_dict_ids({
- 'id' : hda.id,
- 'history_id': hda.history.id,
- 'hid' : hda.hid,
- 'name' : hda.name,
- 'state' : hda.state,
- 'deleted' : hda.deleted,
- 'visible' : hda.visible,
- 'accessible': False
- })
-
- def get_hda_dict_with_error( self, trans, hda=None, history_id=None, id=None, error_msg='Error' ):
- return trans.security.encode_dict_ids({
- 'id' : hda.id if hda else id,
- 'history_id': hda.history.id if hda else history_id,
- 'hid' : hda.hid if hda else '(unknown)',
- 'name' : hda.name if hda else '(unknown)',
- 'error' : error_msg,
- 'state' : trans.model.Dataset.states.NEW
- })
-
def get_display_apps( self, trans, hda ):
display_apps = []
for display_app in hda.get_display_applications( trans ).itervalues():
diff --git a/lib/galaxy/webapps/galaxy/api/datasets.py b/lib/galaxy/webapps/galaxy/api/datasets.py
index 91cfff1b9d0..23af3294179 100644
--- a/lib/galaxy/webapps/galaxy/api/datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/datasets.py
@@ -2,18 +2,35 @@
API operations on the contents of a history dataset.
"""
from galaxy import web
-from galaxy.visualization.data_providers.genome import FeatureLocationIndexDataProvider, SamDataProvider, BamDataProvider
-from galaxy.web.base.controller import BaseAPIController, UsesVisualizationMixin, UsesHistoryDatasetAssociationMixin
-from galaxy.web.base.controller import UsesHistoryMixin
from galaxy.web.framework.helpers import is_true
+from galaxy import util
+
+from galaxy.visualization.data_providers.genome import FeatureLocationIndexDataProvider
+from galaxy.visualization.data_providers.genome import SamDataProvider
+from galaxy.visualization.data_providers.genome import BamDataProvider
from galaxy.datatypes import dataproviders
-from galaxy.util import string_as_bool_or_none
+
+from galaxy.web.base.controller import BaseAPIController
+from galaxy.web.base.controller import UsesVisualizationMixin
+from galaxy.web.base.controller import UsesHistoryDatasetAssociationMixin
+from galaxy.web.base.controller import UsesHistoryMixin
+from galaxy import managers
+
import logging
log = logging.getLogger( __name__ )
+
class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistoryMixin,
UsesHistoryDatasetAssociationMixin ):
+ def __init__( self, app ):
+ super( DatasetsController, self ).__init__( app )
+ self.mgrs = util.bunch.Bunch(
+ histories=managers.histories.HistoryManager( app ),
+ hdas=managers.hdas.HDAManager( app )
+ )
+ self.hda_serializer = managers.hdas.HDASerializer( self.app )
+
@web.expose_api
def index( self, trans, **kwd ):
"""
@@ -55,10 +72,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory
else:
# Default: return dataset as dict.
if hda_ldda == 'hda':
- rval = self.get_hda_dict( trans, dataset )
- # add these to match api/history_contents.py, show
- rval[ 'display_types' ] = self.get_old_display_applications( trans, dataset )
- rval[ 'display_apps' ] = self.get_display_apps( trans, dataset )
+ return self.hda_serializer.serialize_to_view( trans, hda, view=kwd.get( 'view', 'detailed' ) )
else:
rval = dataset.to_dict()
@@ -267,7 +281,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin, UsesHistory
some point in the future without warning. Generally, data should be processed by its
datatype prior to display (the defult if raw is unspecified or explicitly false.
"""
- raw = string_as_bool_or_none( raw )
+ raw = util.string_as_bool_or_none( raw )
rval = ''
try:
hda = self.get_history_dataset_association_from_ids( trans, history_content_id, history_id )
diff --git a/lib/galaxy/webapps/galaxy/api/history_contents.py b/lib/galaxy/webapps/galaxy/api/history_contents.py
index f9738ed2e63..8880c20ff63 100644
--- a/lib/galaxy/webapps/galaxy/api/history_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/history_contents.py
@@ -18,7 +18,10 @@ from galaxy.web.base.controller import UsesTagsMixin
from galaxy.managers import histories
from galaxy.managers import hdas
-from galaxy.managers.collections_util import api_payload_to_create_params, dictify_dataset_collection_instance
+from galaxy.managers.collections_util import api_payload_to_create_params
+from galaxy.managers.collections_util import dictify_dataset_collection_instance
+from galaxy.util import validation
+
import logging
log = logging.getLogger( __name__ )
@@ -33,6 +36,8 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
histories=histories.HistoryManager( app ),
hdas=hdas.HDAManager( app )
)
+ self.hda_serializer = hdas.HDASerializer( app )
+ self.hda_deserializer = hdas.HDADeserializer( app )
def _decode_id( self, trans, id ):
try:
@@ -65,9 +70,6 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
:rtype: list
:returns: dictionaries containing summary or detailed HDA information
- .. seealso::
- :func:`_summary_hda_dict` and
- :func:`galaxy.web.base.controller.UsesHistoryDatasetAssociationMixin.get_hda_dict`
"""
rval = []
@@ -110,10 +112,9 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
detailed = details == 'all' or ( encoded_content_id in details )
if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
- if detailed:
- rval.append( self._detailed_hda_dict( trans, content ) )
- else:
- rval.append( self._summary_hda_dict( trans, history_id, content ) )
+ view = 'detailed' if detailed else 'summary'
+ hda_dict = self.hda_serializer.serialize_to_view( trans, content, view=view )
+ rval.append( hda_dict )
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
if detailed:
@@ -123,62 +124,10 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
return rval
- #TODO: move to model or Mixin
- def _summary_hda_dict( self, trans, encoded_history_id, hda ):
- """
- Returns a dictionary based on the HDA in summary form::
- {
- 'id' : < the encoded dataset id >,
- 'name' : < currently only returns 'file' >,
- 'type' : < name of the dataset >,
- 'url' : < api url to retrieve this datasets full data >,
- }
- """
- api_type = "file"
- encoded_id = trans.security.encode_id( hda.id )
- encoded_dataset_id = trans.security.encode_id( hda.dataset_id );
-
- # TODO: handle failed_metadata here as well
- return {
- 'id' : encoded_id,
- 'history_id' : encoded_history_id,
- 'dataset_id' : encoded_dataset_id,
- 'name' : hda.name,
- 'type' : api_type,
- 'state' : hda.dataset.state,
- 'deleted': hda.deleted,
- 'extension': hda.extension,
- 'visible': hda.visible,
- 'purged': hda.purged,
- 'resubmitted': hda._state == trans.app.model.Dataset.states.RESUBMITTED,
- 'hid' : hda.hid,
- 'history_content_type' : hda.history_content_type,
- #'url' : url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset" ),
- #TODO: this intermittently causes a routes.GenerationException - temp use the legacy route to prevent this
- # see also: https://trello.com/c/5d6j4X5y
- # see also: https://sentry.galaxyproject.org/galaxy/galaxy-main/group/20769/events/9352883/
- 'url' : url_for( 'history_content', history_id=encoded_history_id, id=encoded_id ),
- }
-
def __collection_dict( self, trans, dataset_collection_instance, view="collection" ):
return dictify_dataset_collection_instance( dataset_collection_instance,
security=trans.security, parent=dataset_collection_instance.history, view=view )
- def _detailed_hda_dict( self, trans, hda ):
- """
- Detailed dictionary of hda values.
- """
- try:
- hda_dict = self.get_hda_dict( trans, hda )
- hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda )
- hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda )
- return hda_dict
- except Exception, exc:
- # catch error here - returning a briefer hda_dict with an error attribute
- log.exception( "Error in history API at listing contents with history %s, hda %s: (%s) %s",
- hda.history_id, hda.id, type( exc ), str( exc ) )
- return self.get_hda_dict_with_error( trans, hda=hda, error_msg=str( exc ) )
-
@expose_api_anonymous
def show( self, trans, id, history_id, **kwd ):
"""
@@ -194,7 +143,6 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
:rtype: dict
:returns: dictionary containing detailed HDA information
- .. seealso:: :func:`galaxy.web.base.controller.UsesHistoryDatasetAssociationMixin.get_hda_dict`
"""
contents_type = kwd.get('type', 'dataset')
if contents_type == 'dataset':
@@ -204,6 +152,12 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
else:
return self.__handle_unknown_contents_type( trans, contents_type )
+ def __show_dataset( self, trans, id, **kwd ):
+ hda = self.mgrs.hdas.accessible_by_id( trans, self._decode_id( trans, id ), trans.user )
+ #if hda.history.id != self._decode_id( trans, history_id ):
+ # raise exceptions.ObjectNotFound( 'dataset was not found in this history' )
+ return self.hda_serializer.serialize_to_view( trans, hda, view=kwd.get( 'view', 'detailed' ) )
+
def __show_dataset_collection( self, trans, id, history_id, **kwd ):
try:
service = trans.app.dataset_collections_service
@@ -218,15 +172,6 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
trans.response.status = 500
return { 'error': str( e ) }
- def __show_dataset( self, trans, id, **kwd ):
- hda = self.mgrs.hdas.accessible_by_id( trans, self._decode_id( trans, id ), trans.user )
- #if hda.history.id != self._decode_id( trans, history_id ):
- # raise exceptions.ObjectNotFound( 'dataset was not found in this history' )
- hda_dict = self.get_hda_dict( trans, hda )
- hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda )
- hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda )
- return hda_dict
-
@expose_api_anonymous
def create( self, trans, history_id, payload, **kwd ):
"""
@@ -326,11 +271,7 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
trans.sa_session.flush()
if not hda:
return None
- #TODO: duplicate code - use a serializer with a view
- hda_dict = self.get_hda_dict( trans, hda )
- hda_dict[ 'display_types' ] = self.get_old_display_applications( trans, hda )
- hda_dict[ 'display_apps' ] = self.get_display_apps( trans, hda )
- return hda_dict
+ return self.hda_serializer.serialize_to_view( trans, hda, view=kwd.get( 'view', 'detailed' ) )
def __create_dataset_collection( self, trans, history, payload, **kwd ):
source = kwd.get("source", "new_collection")
@@ -389,38 +330,44 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
return self.__handle_unknown_contents_type( trans, contents_type )
def __update_dataset( self, trans, history_id, id, payload, **kwd ):
- changed = {}
- # anon user
+ # anon user: ensure that history ids match up and the history is the current,
+ # check for uploading, and use only the subset of attribute keys manipulatable by anon users
if trans.user == None:
- if history_id != trans.security.encode_id( trans.history.id ):
- raise exceptions.AuthenticationRequired( 'You must be logged in to update this history' )
+ hda = self.mgrs.hdas.by_id( trans, self._decode_id( trans, id ) )
+ if hda.history != trans.history:
+ raise exceptions.AuthenticationRequired( 'You must be logged in to update this dataset' )
+ hda = self.mgrs.hdas.error_if_uploading( trans, hda )
+
anon_allowed_payload = {}
if 'deleted' in payload:
anon_allowed_payload[ 'deleted' ] = payload[ 'deleted' ]
if 'visible' in payload:
anon_allowed_payload[ 'visible' ] = payload[ 'visible' ]
- payload = self._validate_and_parse_update_payload( anon_allowed_payload )
-
- hda = self.mgrs.hdas.by_id( trans, self._decode_id( trans, id ) )
- hda = self.mgrs.hdas.error_if_uploading( trans, hda )
- if hda.history != trans.history:
- raise exceptions.AuthenticationRequired( 'You must be logged in to update this dataset' )
+ payload = anon_allowed_payload
+ # logged in user: use full payload, check state if deleting, and make sure the history is theirs
else:
- payload = self._validate_and_parse_update_payload( payload )
+ hda = self.mgrs.hdas.accessible_by_id( trans, self._decode_id( trans, id ), trans.user )
+ self.mgrs.histories.error_unless_owner( trans, hda.history, trans.user )
+
# only check_state if not deleting, otherwise cannot delete uploading files
check_state = not payload.get( 'deleted', False )
- hda = self.mgrs.hdas.accessible_by_id( trans, self._decode_id( trans, id ), trans.user )
if check_state:
hda = self.mgrs.hdas.error_if_uploading( trans, hda )
- #hda = self.get_dataset( trans, id, check_ownership=True, check_accessible=True, check_state=check_state )
+ # make the actual changes
+ #TODO: to deserializer
+ #TODO: is this if still needed?
if hda and isinstance( hda, trans.model.HistoryDatasetAssociation ):
- changed = self.set_hda_from_dict( trans, hda, payload )
+ #payload = self._validate_and_parse_update_payload( payload )
+ #changed = self.set_hda_from_dict( trans, hda, payload )
+ self.hda_deserializer.deserialize( trans, hda, payload )
+ #TODO: this should be an effect of deleting the hda
if payload.get( 'deleted', False ):
self.stop_hda_creating_job( hda )
+ return self.hda_serializer.serialize_to_view( trans, hda, view=kwd.get( 'view', 'detailed' ) )
- return changed
+ return {}
def __update_dataset_collection( self, trans, history_id, id, payload, **kwd ):
return trans.app.dataset_collections_service.update( trans, "history", id, payload )
@@ -473,68 +420,74 @@ class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociatio
self.mgrs.histories.error_unless_ownership( trans, hda.history, trans.user )
self.mgrs.hdas.error_if_uploading( trans, hda )
- rval = { 'id' : id }
- hda.deleted = True
if purge:
- if not trans.app.config.allow_user_dataset_purge:
- raise exceptions.ConfigDoesNotAllowException( 'This instance does not allow user dataset purging' )
- hda.purged = True
- trans.sa_session.add( hda )
- trans.sa_session.flush()
- if hda.dataset.user_can_purge:
- try:
- hda.dataset.full_delete()
- trans.sa_session.add( hda.dataset )
- except:
- pass
- # flush now to preserve deleted state in case of later interruption
- trans.sa_session.flush()
- rval[ 'purged' ] = True
+ self.mgrs.hdas.purge( trans, hda )
+ else:
+ self.mgrs.hdas.delete( trans, hda )
+ return self.hda_serializer.serialize_to_view( trans, hda, view=kwd.get( 'view', 'detailed' ) )
- self.stop_hda_creating_job( hda )
- trans.sa_session.flush()
+ #rval = { 'id' : id }
+ #hda.deleted = True
+ #if purge:
+ # if not trans.app.config.allow_user_dataset_purge:
+ # raise exceptions.ConfigDoesNotAllowException( 'This instance does not allow user dataset purging' )
+ # hda.purged = True
+ # trans.sa_session.add( hda )
+ # trans.sa_session.flush()
+ # if hda.dataset.user_can_purge:
+ # try:
+ # hda.dataset.full_delete()
+ # trans.sa_session.add( hda.dataset )
+ # except:
+ # pass
+ # # flush now to preserve deleted state in case of later interruption
+ # trans.sa_session.flush()
+ # rval[ 'purged' ] = True
+ #
+ #self.stop_hda_creating_job( hda )
+ #trans.sa_session.flush()
+ #
+ #rval[ 'deleted' ] = True
+ #return rval
- rval[ 'deleted' ] = True
- return rval
-
- def _validate_and_parse_update_payload( self, payload ):
- """
- Validate and parse incomming data payload for an HDA.
- """
- # This layer handles (most of the stricter idiot proofing):
- # - unknown/unallowed keys
- # - changing data keys from api key to attribute name
- # - protection against bad data form/type
- # - protection against malicious data content
- # all other conversions and processing (such as permissions, etc.) should happen down the line
-
- # keys listed here don't error when attempting to set, but fail silently
- # this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs
- valid_but_uneditable_keys = (
- 'id', 'name', 'type', 'api_type', 'model_class', 'history_id', 'hid',
- 'accessible', 'purged', 'state', 'data_type', 'file_ext', 'file_size', 'misc_blurb',
- 'download_url', 'visualizations', 'display_apps', 'display_types',
- 'metadata_dbkey', 'metadata_column_names', 'metadata_column_types', 'metadata_columns',
- 'metadata_comment_lines', 'metadata_data_lines'
- )
- validated_payload = {}
- for key, val in payload.items():
- if val is None:
- continue
- if key in ( 'name', 'genome_build', 'misc_info', 'annotation' ):
- val = self.validate_and_sanitize_basestring( key, val )
- #TODO: need better remap system or eliminate the need altogether
- key = 'dbkey' if key == 'genome_build' else key
- key = 'info' if key == 'misc_info' else key
- validated_payload[ key ] = val
- if key in ( 'deleted', 'visible' ):
- validated_payload[ key ] = self.validate_boolean( key, val )
- elif key == 'tags':
- validated_payload[ key ] = self.validate_and_sanitize_basestring_list( key, val )
- elif key not in valid_but_uneditable_keys:
- pass
- #log.warn( 'unknown key: %s', str( key ) )
- return validated_payload
+ #def _validate_and_parse_update_payload( self, payload ):
+ # """
+ # Validate and parse incomming data payload for an HDA.
+ # """
+ # # This layer handles (most of the stricter idiot proofing):
+ # # - unknown/unallowed keys
+ # # - changing data keys from api key to attribute name
+ # # - protection against bad data form/type
+ # # - protection against malicious data content
+ # # all other conversions and processing (such as permissions, etc.) should happen down the line
+ #
+ # # keys listed here don't error when attempting to set, but fail silently
+ # # this allows PUT'ing an entire model back to the server without attribute errors on uneditable attrs
+ # valid_but_uneditable_keys = (
+ # 'id', 'name', 'type', 'api_type', 'model_class', 'history_id', 'hid',
+ # 'accessible', 'purged', 'state', 'data_type', 'file_ext', 'file_size', 'misc_blurb',
+ # 'download_url', 'visualizations', 'display_apps', 'display_types',
+ # 'metadata_dbkey', 'metadata_column_names', 'metadata_column_types', 'metadata_columns',
+ # 'metadata_comment_lines', 'metadata_data_lines'
+ # )
+ # validated_payload = {}
+ # for key, val in payload.items():
+ # if val is None:
+ # continue
+ # if key in ( 'name', 'genome_build', 'misc_info', 'annotation' ):
+ # val = validation.validate_and_sanitize_basestring( key, val )
+ # #TODO: need better remap system or eliminate the need altogether
+ # key = 'dbkey' if key == 'genome_build' else key
+ # key = 'info' if key == 'misc_info' else key
+ # validated_payload[ key ] = val
+ # if key in ( 'deleted', 'visible' ):
+ # validated_payload[ key ] = validation.validate_boolean( key, val )
+ # elif key == 'tags':
+ # validated_payload[ key ] = validation.validate_and_sanitize_basestring_list( key, val )
+ # elif key not in valid_but_uneditable_keys:
+ # pass
+ # #log.warn( 'unknown key: %s', str( key ) )
+ # return validated_payload
def __handle_unknown_contents_type( self, trans, contents_type ):
raise exceptions.UnknownContentsType('Unknown contents type: %s' % type)
diff --git a/lib/galaxy/webapps/galaxy/controllers/history.py b/lib/galaxy/webapps/galaxy/controllers/history.py
index eae97af6de8..2f526bb1b7c 100644
--- a/lib/galaxy/webapps/galaxy/controllers/history.py
+++ b/lib/galaxy/webapps/galaxy/controllers/history.py
@@ -9,17 +9,24 @@ from galaxy import web
from galaxy import exceptions
from galaxy import managers
from galaxy.datatypes.data import nice_size
-from galaxy.model.item_attrs import UsesAnnotations, UsesItemRatings
from galaxy.model.orm import and_, eagerload_all, func
from galaxy import util
from galaxy.util import Params
from galaxy.util.odict import odict
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import error, url_for
-from galaxy.web.base.controller import BaseUIController, SharableMixin, UsesHistoryDatasetAssociationMixin, UsesHistoryMixin
+
+from galaxy.web.base.controller import BaseUIController
+from galaxy.web.base.controller import SharableMixin
+from galaxy.web.base.controller import UsesHistoryDatasetAssociationMixin
+from galaxy.web.base.controller import UsesHistoryMixin
from galaxy.web.base.controller import ExportsHistoryMixin
from galaxy.web.base.controller import ImportsHistoryMixin
+from galaxy.model.item_attrs import UsesAnnotations
+from galaxy.model.item_attrs import UsesItemRatings
+
from galaxy.web.base.controller import ERROR, INFO, SUCCESS, WARNING
+
from galaxy.web.framework.helpers import grids, iff, time_ago, escape
log = logging.getLogger( __name__ )
diff --git a/test/unit/managers/test_DatasetManager.py b/test/unit/managers/test_DatasetManager.py
index 6a65b601e04..98bef00103e 100644
--- a/test/unit/managers/test_DatasetManager.py
+++ b/test/unit/managers/test_DatasetManager.py
@@ -20,6 +20,9 @@ from galaxy.util.bunch import Bunch
import mock
from test_ModelManager import BaseTestCase
from galaxy.managers.datasets import DatasetManager
+from galaxy.managers.datasets import DatasetAssociationManager
+from galaxy.managers.histories import HistoryManager
+from galaxy.managers.hdas import HDAManager
# =============================================================================
@@ -29,83 +32,101 @@ user3_data = dict( email='user3@user3.user3', username='user3', password=default
# =============================================================================
-class DatasetManagerTestCase( BaseTestCase ):
+#class DatasetManagerTestCase( BaseTestCase ):
+#
+# def set_up_managers( self ):
+# super( DatasetManagerTestCase, self ).set_up_managers()
+# self.dataset_mgr = DatasetManager( self.app )
+#
+# def test_create( self ):
+# print "should be able to create a new Dataset"
+# dataset1 = self.dataset_mgr.create( self.trans )
+# self.assertIsInstance( dataset1, model.Dataset )
+# self.assertEqual( dataset1, self.trans.sa_session.query( model.Dataset ).get( dataset1.id ) )
+#
+# def test_base( self ):
+# dataset1 = self.dataset_mgr.create( self.trans )
+# dataset2 = self.dataset_mgr.create( self.trans )
+#
+# print "should be able to query"
+# datasets = self.trans.sa_session.query( model.Dataset ).all()
+# self.assertEqual( self.dataset_mgr.list( self.trans ), datasets )
+# self.assertEqual( self.dataset_mgr.one( self.trans, filters=( model.Dataset.id == dataset1.id ) ), dataset1 )
+# self.assertEqual( self.dataset_mgr.by_id( self.trans, dataset1.id ), dataset1 )
+# self.assertEqual( self.dataset_mgr.by_ids( self.trans, [ dataset2.id, dataset1.id ] ), [ dataset2, dataset1 ] )
+#
+# print "should be able to limit and offset"
+# self.assertEqual( self.dataset_mgr.list( self.trans, limit=1 ), datasets[0:1] )
+# self.assertEqual( self.dataset_mgr.list( self.trans, offset=1 ), datasets[1:] )
+# self.assertEqual( self.dataset_mgr.list( self.trans, limit=1, offset=1 ), datasets[1:2] )
+#
+# self.assertEqual( self.dataset_mgr.list( self.trans, limit=0 ), [] )
+# self.assertEqual( self.dataset_mgr.list( self.trans, offset=3 ), [] )
+#
+# print "should be able to order"
+# self.assertEqual( self.dataset_mgr.list( self.trans, order_by=sqlalchemy.desc( model.Dataset.create_time ) ),
+# [ dataset2, dataset1 ] )
+#
+# def test_delete( self ):
+# item1 = self.dataset_mgr.create( self.trans )
+#
+# print "should be able to delete and undelete an hda"
+# self.assertFalse( item1.deleted )
+# self.assertEqual( self.dataset_mgr.delete( self.trans, item1 ), item1 )
+# self.assertTrue( item1.deleted )
+# self.assertEqual( self.dataset_mgr.undelete( self.trans, item1 ), item1 )
+# self.assertFalse( item1.deleted )
+#
+# def test_purge_allowed( self ):
+# self.trans.app.config.allow_user_dataset_purge = True
+# item1 = self.dataset_mgr.create( self.trans )
+#
+# print "should purge an hda if config does allow"
+# self.assertFalse( item1.purged )
+# self.assertEqual( self.dataset_mgr.purge( self.trans, item1 ), item1 )
+# self.assertTrue( item1.purged )
+#
+# def test_purge_not_allowed( self ):
+# self.trans.app.config.allow_user_dataset_purge = False
+# item1 = self.dataset_mgr.create( self.trans )
+#
+# print "should raise an error when purging an hda if config does not allow"
+# self.assertFalse( item1.purged )
+# self.assertRaises( exceptions.ConfigDoesNotAllowException, self.dataset_mgr.purge, self.trans, item1 )
+# self.assertFalse( item1.purged )
+#
+# def test_accessible( self ):
+# owner = self.user_mgr.create( self.trans, **user2_data )
+# non_owner = self.user_mgr.create( self.trans, **user3_data )
+#
+# dataset = self.dataset_mgr.create( self.trans )
+#
+# print "(by default, dataset permissions are lax) should be accessible to all"
+# for user in self.user_mgr.list( self.trans ):
+# self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, user ) )
+#
+# #TODO: this is ... not working...
+# self.dataset_mgr.give_access_permission( self.trans, dataset, owner )
+# self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, owner ) )
+# self.assertFalse( self.dataset_mgr.is_accessible( self.trans, dataset, non_owner ) )
+
+
+# =============================================================================
+class DatasetAssociationManagerTestCase( BaseTestCase ):
def set_up_managers( self ):
- super( DatasetManagerTestCase, self ).set_up_managers()
- self.dataset_mgr = DatasetManager( self.app )
+ super( DatasetAssociationManagerTestCase, self ).set_up_managers()
+ self.dataset_mgr = DatasetAssociationManager( self.app )
+ self.hda_mgr = HDAManager( self.app )
+ self.history_mgr = HistoryManager( self.app )
- def test_create( self ):
- print "should be able to create a new Dataset"
- dataset1 = self.dataset_mgr.create( self.trans )
- self.assertIsInstance( dataset1, model.Dataset )
- self.assertEqual( dataset1, self.trans.sa_session.query( model.Dataset ).get( dataset1.id ) )
-
- def test_base( self ):
- dataset1 = self.dataset_mgr.create( self.trans )
- dataset2 = self.dataset_mgr.create( self.trans )
-
- print "should be able to query"
- datasets = self.trans.sa_session.query( model.Dataset ).all()
- self.assertEqual( self.dataset_mgr.list( self.trans ), datasets )
- self.assertEqual( self.dataset_mgr.one( self.trans, filters=( model.Dataset.id == dataset1.id ) ), dataset1 )
- self.assertEqual( self.dataset_mgr.by_id( self.trans, dataset1.id ), dataset1 )
- self.assertEqual( self.dataset_mgr.by_ids( self.trans, [ dataset2.id, dataset1.id ] ), [ dataset2, dataset1 ] )
-
- print "should be able to limit and offset"
- self.assertEqual( self.dataset_mgr.list( self.trans, limit=1 ), datasets[0:1] )
- self.assertEqual( self.dataset_mgr.list( self.trans, offset=1 ), datasets[1:] )
- self.assertEqual( self.dataset_mgr.list( self.trans, limit=1, offset=1 ), datasets[1:2] )
-
- self.assertEqual( self.dataset_mgr.list( self.trans, limit=0 ), [] )
- self.assertEqual( self.dataset_mgr.list( self.trans, offset=3 ), [] )
-
- print "should be able to order"
- self.assertEqual( self.dataset_mgr.list( self.trans, order_by=sqlalchemy.desc( model.Dataset.create_time ) ),
- [ dataset2, dataset1 ] )
-
- def test_delete( self ):
- item1 = self.dataset_mgr.create( self.trans )
-
- print "should be able to delete and undelete an hda"
- self.assertFalse( item1.deleted )
- self.assertEqual( self.dataset_mgr.delete( self.trans, item1 ), item1 )
- self.assertTrue( item1.deleted )
- self.assertEqual( self.dataset_mgr.undelete( self.trans, item1 ), item1 )
- self.assertFalse( item1.deleted )
-
- def test_purge_allowed( self ):
- self.trans.app.config.allow_user_dataset_purge = True
- item1 = self.dataset_mgr.create( self.trans )
-
- print "should purge an hda if config does allow"
- self.assertFalse( item1.purged )
- self.assertEqual( self.dataset_mgr.purge( self.trans, item1 ), item1 )
- self.assertTrue( item1.purged )
-
- def test_purge_not_allowed( self ):
- self.trans.app.config.allow_user_dataset_purge = False
- item1 = self.dataset_mgr.create( self.trans )
-
- print "should raise an error when purging an hda if config does not allow"
- self.assertFalse( item1.purged )
- self.assertRaises( exceptions.ConfigDoesNotAllowException, self.dataset_mgr.purge, self.trans, item1 )
- self.assertFalse( item1.purged )
-
- def test_accessible( self ):
+ def test_metadata( self ):
owner = self.user_mgr.create( self.trans, **user2_data )
- non_owner = self.user_mgr.create( self.trans, **user3_data )
+ history1 = self.history_mgr.create( self.trans, name='history1', user=owner )
+ dataset1 = self.dataset_mgr.create( self.trans )
- dataset = self.dataset_mgr.create( self.trans )
-
- print "(by default, dataset permissions are lax) should be accessible to all"
- for user in self.user_mgr.list( self.trans ):
- self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, user ) )
-
-#TODO: this is ... not working...
- self.dataset_mgr.give_access_permission( self.trans, dataset, owner )
- self.assertTrue( self.dataset_mgr.is_accessible( self.trans, dataset, owner ) )
- self.assertFalse( self.dataset_mgr.is_accessible( self.trans, dataset, non_owner ) )
+ print "should be able to create a new HDA with a specified history and dataset"
+ hda1 = self.hda_mgr.create( self.trans, history=history1, dataset=dataset1, hid=1 )
# =============================================================================
From ca370e3cfcd3687b0d6f244f596d141a823488a2 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:25 -0500
Subject: [PATCH 017/473] Remove some unused imports from
lib/galaxy/tools/__init__.py.
---
lib/galaxy/tools/__init__.py | 12 ++----------
1 file changed, 2 insertions(+), 10 deletions(-)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index ecfa87e5a45..ed67e1f5658 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -7,20 +7,15 @@ import glob
import json
import logging
import os
-import pipes
import re
import shutil
-import sys
import string
import tarfile
import tempfile
import threading
-import traceback
import types
import urllib
-from math import isinf
-
from galaxy import eggs
eggs.require( "MarkupSafe" ) # MarkupSafe must load before mako
eggs.require( "Mako" )
@@ -35,10 +30,8 @@ from paste import httpexceptions
from sqlalchemy import and_
from galaxy import jobs, model
-from galaxy.jobs.error_level import StdioErrorLevel
from galaxy.datatypes.metadata import JobExternalOutputMetadataWrapper
from galaxy import exceptions
-from galaxy.jobs import ParallelismInfo
from galaxy.tools import watcher
from galaxy.tools.actions import DefaultToolAction
from galaxy.tools.actions.data_source import DataSourceToolAction
@@ -58,12 +51,11 @@ from galaxy.tools.filters import FilterFactory
from galaxy.tools.test import parse_tests
from galaxy.tools.parser import get_tool_source
from galaxy.tools.parser.xml import XmlPageSource
-from galaxy.util import listify, parse_xml, rst_to_html, string_as_bool, string_to_object, xml_text, xml_to_string
+from galaxy.util import listify, parse_xml, rst_to_html, string_as_bool, string_to_object
from galaxy.tools.parameters.meta import expand_meta_parameters
from galaxy.util.bunch import Bunch
from galaxy.util.expressions import ExpressionContext
from galaxy.util.hash_util import hmac_new
-from galaxy.util.none_like import NoneDataset
from galaxy.util.odict import odict
from galaxy.util.template import fill_template
from galaxy.web import url_for
@@ -73,7 +65,7 @@ from galaxy.model.item_attrs import Dictifiable
from galaxy.model import Workflow
from tool_shed.util import common_util
from tool_shed.util import shed_util_common as suc
-from .loader import load_tool, template_macro_params, raw_tool_xml_tree, imported_macro_paths
+from .loader import template_macro_params, raw_tool_xml_tree, imported_macro_paths
from .execute import execute as execute_job
from .wrappers import (
ToolParameterValueWrapper,
From 7fa267ebcdc601567ea8af7c84ebc5cb42c4f95b Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:25 -0500
Subject: [PATCH 018/473] More PEP-8 style tweaks to
lib/galaxy/tools/__init__.py.
---
lib/galaxy/tools/__init__.py | 57 ++++++++++++++++++++----------------
1 file changed, 31 insertions(+), 26 deletions(-)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index ed67e1f5658..40213724a46 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -257,8 +257,9 @@ class ToolBox( object, Dictifiable ):
return tool_path
def __add_tool_to_tool_panel( self, tool, panel_component, section=False ):
- # See if a version of this tool is already loaded into the tool panel. The value of panel_component
- # will be a ToolSection (if the value of section=True) or self.tool_panel (if section=False).
+ # See if a version of this tool is already loaded into the tool panel.
+ # The value of panel_component will be a ToolSection (if the value of
+ # section=True) or self.tool_panel (if section=False).
tool_id = str( tool.id )
tool = self.tools_by_id[ tool_id ]
if section:
@@ -300,13 +301,17 @@ class ToolBox( object, Dictifiable ):
if not inserted:
inserted = True
if not inserted:
- if tool.guid is None or \
- tool.tool_shed is None or \
- tool.repository_name is None or \
- tool.repository_owner is None or \
- tool.installed_changeset_revision is None:
- # We have a tool that was not installed from the Tool Shed, but is also not yet defined in
- # integrated_tool_panel.xml, so append it to the tool panel.
+ if (
+ tool.guid is None or
+ tool.tool_shed is None or
+ tool.repository_name is None or
+ tool.repository_owner is None or
+ tool.installed_changeset_revision is None
+ ):
+ # We have a tool that was not installed from the Tool
+ # Shed, but is also not yet defined in
+ # integrated_tool_panel.xml, so append it to the tool
+ # panel.
panel_dict[ key ] = tool
log.debug( "Loaded tool id: %s, version: %s into tool panel.." % ( tool.id, tool.version ) )
else:
@@ -364,9 +369,10 @@ class ToolBox( object, Dictifiable ):
def load_integrated_tool_panel_keys( self ):
"""
- Load the integrated tool panel keys, setting values for tools and workflows to None. The values will
- be reset when the various tool panel config files are parsed, at which time the tools and workflows are
- loaded.
+ Load the integrated tool panel keys, setting values for tools and
+ workflows to None. The values will be reset when the various tool
+ panel config files are parsed, at which time the tools and workflows
+ are loaded.
"""
tree = parse_xml( self.integrated_tool_panel_config )
root = tree.getroot()
@@ -499,11 +505,11 @@ class ToolBox( object, Dictifiable ):
# We store only the port, if one exists, in the database.
tool_shed = common_util.remove_protocol_from_tool_shed_url( tool_shed )
return self.app.install_model.context.query( self.app.install_model.ToolShedRepository ) \
- .filter( and_( self.app.install_model.ToolShedRepository.table.c.tool_shed == tool_shed,
- self.app.install_model.ToolShedRepository.table.c.name == name,
- self.app.install_model.ToolShedRepository.table.c.owner == owner,
- self.app.install_model.ToolShedRepository.table.c.installed_changeset_revision == installed_changeset_revision ) ) \
- .first()
+ .filter( and_( self.app.install_model.ToolShedRepository.table.c.tool_shed == tool_shed,
+ self.app.install_model.ToolShedRepository.table.c.name == name,
+ self.app.install_model.ToolShedRepository.table.c.owner == owner,
+ self.app.install_model.ToolShedRepository.table.c.installed_changeset_revision == installed_changeset_revision ) ) \
+ .first()
def get_tool_components( self, tool_id, tool_version=None, get_loaded_tools_by_lineage=False, set_selected=False ):
"""
@@ -627,12 +633,14 @@ class ToolBox( object, Dictifiable ):
log.exception( "Error reading tool from path: %s" % path )
def __add_tool( self, tool, load_panel_dict, panel_dict ):
- # Allow for the same tool to be loaded into multiple places in the tool panel. We have to handle
- # the case where the tool is contained in a repository installed from the tool shed, and the Galaxy
- # administrator has retrieved updates to the installed repository. In this case, the tool may have
- # been updated, but the version was not changed, so the tool should always be reloaded here. We used
- # to only load the tool if it was not found in self.tools_by_id, but performing that check did
- # not enable this scenario.
+ # Allow for the same tool to be loaded into multiple places in the
+ # tool panel. We have to handle the case where the tool is contained
+ # in a repository installed from the tool shed, and the Galaxy
+ # administrator has retrieved updates to the installed repository. In
+ # this case, the tool may have been updated, but the version was not
+ # changed, so the tool should always be reloaded here. We used to
+ # only load the tool if it was not found in self.tools_by_id, but
+ # performing that check did not enable this scenario.
self.tools_by_id[ tool.id ] = tool
if load_panel_dict:
self.__add_tool_to_tool_panel( tool, panel_dict, section=isinstance( panel_dict, ToolSection ) )
@@ -787,8 +795,6 @@ class ToolBox( object, Dictifiable ):
:param tool_id: the tool ID from app.toolbox
:returns: tuple of tarball filename, success True/False, message/None
"""
- message = ''
- success = True
# Make sure the tool is actually loaded.
if tool_id not in self.tools_by_id:
return None, False, "No tool with id %s" % escape( tool_id )
@@ -870,7 +876,6 @@ class ToolBox( object, Dictifiable ):
if os.path.exists( sample_file ):
tarfile_path, tarfile_name = os.path.split( tar_file )
tarfile_path = os.path.join( 'tool-data', tarfile_name )
- sample_name = tarfile_path + '.sample'
tarball_files.append( ( sample_file, tarfile_path ) )
data_table_definitions.append( data_table.xml_string )
if len( data_table_definitions ) > 0:
From a23846839b0fc16e8ca98bdff60131c9b98111dc Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:26 -0500
Subject: [PATCH 019/473] Small simplifications to
lib/galaxy/tools/__init__.py.
---
lib/galaxy/tools/__init__.py | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 40213724a46..2491fc149dd 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -289,8 +289,7 @@ class ToolBox( object, Dictifiable ):
for index, integrated_panel_key in enumerate( self.integrated_tool_panel.keys() ):
if key == integrated_panel_key:
panel_dict.insert( index, key, tool )
- if not inserted:
- inserted = True
+ inserted = True
if not inserted:
# Check the tool's installed versions.
for lineage_id in tool.lineage_ids:
@@ -298,8 +297,7 @@ class ToolBox( object, Dictifiable ):
for index, integrated_panel_key in enumerate( self.integrated_tool_panel.keys() ):
if lineage_id_key == integrated_panel_key:
panel_dict.insert( index, key, tool )
- if not inserted:
- inserted = True
+ inserted = True
if not inserted:
if (
tool.guid is None or
@@ -322,8 +320,7 @@ class ToolBox( object, Dictifiable ):
if lineage_id in self.tools_by_id:
loaded_version_key = 'tool_%s' % lineage_id
if loaded_version_key in panel_dict:
- if not already_loaded:
- already_loaded = True
+ already_loaded = True
if not already_loaded:
# If the tool is not defined in integrated_tool_panel.xml, append it to the tool panel.
panel_dict[ key ] = tool
From 9d899a06543d19e03f52a7276a8a7c1f84f7e9b8 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:26 -0500
Subject: [PATCH 020/473] Remove broken method fix_integrated_tool_panel_dict.
As of 5f6c670 (Jan 10, 2014) this does nothing (``if section_value is None``
followed by ``if isinstance( section_value, Tool ):``) and nobody seemed to
notice - so I assume it is unneeded. If still needed - someone needs to fix
it :).
---
lib/galaxy/tools/__init__.py | 17 -----------------
1 file changed, 17 deletions(-)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 2491fc149dd..8720be852ea 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -163,24 +163,8 @@ class ToolBox( object, Dictifiable ):
# This will cover cases where the Galaxy administrator manually edited one or more of the tool panel
# config files, adding or removing locally developed tools or workflows. The value of integrated_tool_panel
# will be False when things like functional tests are the caller.
- self.fix_integrated_tool_panel_dict()
self.write_integrated_tool_panel_config_file()
- def fix_integrated_tool_panel_dict( self ):
- # HACK: instead of fixing after the fact, I suggest some combination of:
- # 1) adjusting init_tools() and called methods to get this right
- # 2) redesigning the code and/or data structure used to read/write integrated_tool_panel.xml
- for key, value in self.integrated_tool_panel.iteritems():
- if isinstance( value, ToolSection ):
- for section_key, section_value in value.elems.iteritems():
- if section_value is None:
- if isinstance( section_value, Tool ):
- tool_id = section_key[5:]
- value.elems[section_key] = self.tools_by_id.get( tool_id )
- elif isinstance( section_value, Workflow ):
- workflow_id = section_key[9:]
- value.elems[section_key] = self.workflows_by_id.get( workflow_id )
-
def init_tools( self, config_filename ):
"""
Read the configuration file and load each tool. The following tags are currently supported:
@@ -723,7 +707,6 @@ class ToolBox( object, Dictifiable ):
# This will cover cases where the Galaxy administrator manually edited one or more of the tool panel
# config files, adding or removing locally developed tools or workflows. The value of integrated_tool_panel
# will be False when things like functional tests are the caller.
- self.fix_integrated_tool_panel_dict()
self.write_integrated_tool_panel_config_file()
return tool.id
except Exception:
From 92e6a8e4fffd827f7f46da53a71b647b31f5906d Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:27 -0500
Subject: [PATCH 021/473] Slightly rework lib/galaxy/workflow/modules.py for
readability.
---
lib/galaxy/workflow/modules.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/lib/galaxy/workflow/modules.py b/lib/galaxy/workflow/modules.py
index 7fa12e7e8c8..d86f167aaa9 100644
--- a/lib/galaxy/workflow/modules.py
+++ b/lib/galaxy/workflow/modules.py
@@ -506,16 +506,17 @@ class ToolModule( WorkflowModule ):
@classmethod
def from_workflow_step( Class, trans, step ):
+ toolbox = trans.app.toolbox
tool_id = step.tool_id
- if trans.app.toolbox and tool_id not in trans.app.toolbox.tools_by_id:
+ if toolbox and tool_id not in toolbox.tools_by_id:
# See if we have access to a different version of the tool.
# TODO: If workflows are ever enhanced to use tool version
# in addition to tool id, enhance the selection process here
# to retrieve the correct version of the tool.
- tool = trans.app.toolbox.get_tool( tool_id )
+ tool = toolbox.get_tool( tool_id )
if tool:
tool_id = tool.id
- if ( trans.app.toolbox and tool_id in trans.app.toolbox.tools_by_id ):
+ if ( toolbox and tool_id in toolbox.tools_by_id ):
if step.config:
# This step has its state saved in the config field due to the
# tool being previously unavailable.
From 917175d2370e6e55164437a55431821cffc1b6d9 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:27 -0500
Subject: [PATCH 022/473] Rework toolbox 'fix' tool tags when multiple configs
are loaded. Looks like the tool tag stuff still assumed there was just one
config file - that said toolbox doesn't have a self.sa_session so I think
tool tags are broken and have been for some time.
---
lib/galaxy/tools/__init__.py | 41 +++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 17 deletions(-)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 8720be852ea..162395793e3 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -143,18 +143,7 @@ class ToolBox( object, Dictifiable ):
self.tool_watcher = watcher.get_watcher( self, app.config )
self.filter_factory = FilterFactory( self )
self.init_dependency_manager()
- config_filenames = listify( config_filenames )
- for config_filename in config_filenames:
- if os.path.isdir( config_filename ):
- directory_contents = sorted( os.listdir( config_filename ) )
- directory_config_files = [ config_file for config_file in directory_contents if config_file.endswith( ".xml" ) ]
- config_filenames.remove( config_filename )
- config_filenames.extend( directory_config_files )
- for config_filename in config_filenames:
- try:
- self.init_tools( config_filename )
- except:
- log.exception( "Error loading tools defined in config %s", config_filename )
+ self.init_tools_from_configs( config_filenames )
if self.app.name == 'galaxy' and self.integrated_tool_panel_config_has_contents:
# Load self.tool_panel based on the order in self.integrated_tool_panel.
self.load_tool_panel()
@@ -165,7 +154,29 @@ class ToolBox( object, Dictifiable ):
# will be False when things like functional tests are the caller.
self.write_integrated_tool_panel_config_file()
- def init_tools( self, config_filename ):
+ def init_tools_from_configs( self, config_filenames ):
+ """ Read through all tool config files and initialize tools in each
+ with init_tools_from_config below.
+ """
+ if self.app.config.get_bool( 'enable_tool_tags', False ):
+ log.info("removing all tool tag associations (" + str( self.sa_session.query( self.app.model.ToolTagAssociation ).count() ) + ")" )
+ self.sa_session.query( self.app.model.ToolTagAssociation ).delete()
+ self.sa_session.flush()
+
+ config_filenames = listify( config_filenames )
+ for config_filename in config_filenames:
+ if os.path.isdir( config_filename ):
+ directory_contents = sorted( os.listdir( config_filename ) )
+ directory_config_files = [ config_file for config_file in directory_contents if config_file.endswith( ".xml" ) ]
+ config_filenames.remove( config_filename )
+ config_filenames.extend( directory_config_files )
+ for config_filename in config_filenames:
+ try:
+ self.init_tools_from_config( config_filename )
+ except:
+ log.exception( "Error loading tools defined in config %s", config_filename )
+
+ def init_tools_from_config( self, config_filename ):
"""
Read the configuration file and load each tool. The following tags are currently supported:
@@ -183,10 +194,6 @@ class ToolBox( object, Dictifiable ):
"""
- if self.app.config.get_bool( 'enable_tool_tags', False ):
- log.info("removing all tool tag associations (" + str( self.sa_session.query( self.app.model.ToolTagAssociation ).count() ) + ")" )
- self.sa_session.query( self.app.model.ToolTagAssociation ).delete()
- self.sa_session.flush()
log.info( "Parsing the tool configuration %s" % config_filename )
tree = parse_xml( config_filename )
root = tree.getroot()
From 34e7ac89c5a749bb92d4dadce9619ec4cc4ea037 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:28 -0500
Subject: [PATCH 023/473] Refactor tool tag handling out of toolbox. Pretty
sure this had been broken (toolbox didn't have a self.sa_session). Anyway -
this probably didn't fix that but at least now the big read tool tag loop is
more readable (cyclomatic complexity of that method down from 18 to 12).
---
lib/galaxy/tools/__init__.py | 30 ++---------
lib/galaxy/tools/toolbox/__init__.py | 7 +++
lib/galaxy/tools/toolbox/tags.py | 76 ++++++++++++++++++++++++++++
3 files changed, 87 insertions(+), 26 deletions(-)
create mode 100644 lib/galaxy/tools/toolbox/__init__.py
create mode 100644 lib/galaxy/tools/toolbox/tags.py
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 162395793e3..c200e062395 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -51,6 +51,7 @@ from galaxy.tools.filters import FilterFactory
from galaxy.tools.test import parse_tests
from galaxy.tools.parser import get_tool_source
from galaxy.tools.parser.xml import XmlPageSource
+from galaxy.tools.toolbox import tool_tag_manager
from galaxy.util import listify, parse_xml, rst_to_html, string_as_bool, string_to_object
from galaxy.tools.parameters.meta import expand_meta_parameters
from galaxy.util.bunch import Bunch
@@ -143,6 +144,7 @@ class ToolBox( object, Dictifiable ):
self.tool_watcher = watcher.get_watcher( self, app.config )
self.filter_factory = FilterFactory( self )
self.init_dependency_manager()
+ self.tool_tag_manager = tool_tag_manager( app )
self.init_tools_from_configs( config_filenames )
if self.app.name == 'galaxy' and self.integrated_tool_panel_config_has_contents:
# Load self.tool_panel based on the order in self.integrated_tool_panel.
@@ -158,11 +160,7 @@ class ToolBox( object, Dictifiable ):
""" Read through all tool config files and initialize tools in each
with init_tools_from_config below.
"""
- if self.app.config.get_bool( 'enable_tool_tags', False ):
- log.info("removing all tool tag associations (" + str( self.sa_session.query( self.app.model.ToolTagAssociation ).count() ) + ")" )
- self.sa_session.query( self.app.model.ToolTagAssociation ).delete()
- self.sa_session.flush()
-
+ self.tool_tag_manager.reset_tags()
config_filenames = listify( config_filenames )
for config_filename in config_filenames:
if os.path.isdir( config_filename ):
@@ -588,27 +586,7 @@ class ToolBox( object, Dictifiable ):
self.app.install_model.context.flush()
# Load the tool's lineage ids.
tool.lineage_ids = tool.tool_version.get_version_ids( self.app )
- if self.app.config.get_bool( 'enable_tool_tags', False ):
- tag_names = elem.get( "tags", "" ).split( "," )
- for tag_name in tag_names:
- if tag_name == '':
- continue
- tag = self.sa_session.query( self.app.model.Tag ).filter_by( name=tag_name ).first()
- if not tag:
- tag = self.app.model.Tag( name=tag_name )
- self.sa_session.add( tag )
- self.sa_session.flush()
- tta = self.app.model.ToolTagAssociation( tool_id=tool.id, tag_id=tag.id )
- self.sa_session.add( tta )
- self.sa_session.flush()
- else:
- for tagged_tool in tag.tagged_tools:
- if tagged_tool.tool_id == tool.id:
- break
- else:
- tta = self.app.model.ToolTagAssociation( tool_id=tool.id, tag_id=tag.id )
- self.sa_session.add( tta )
- self.sa_session.flush()
+ self.tool_tag_manager.handle_tags( tool.id, elem )
self.__add_tool( tool, load_panel_dict, panel_dict )
# Always load the tool into the integrated_panel_dict, or it will not be included in the integrated_tool_panel.xml file.
if key in integrated_panel_dict or index is None:
diff --git a/lib/galaxy/tools/toolbox/__init__.py b/lib/galaxy/tools/toolbox/__init__.py
new file mode 100644
index 00000000000..29bc07741d3
--- /dev/null
+++ b/lib/galaxy/tools/toolbox/__init__.py
@@ -0,0 +1,7 @@
+""" API for this module containing functionality related to the tool box.
+"""
+
+from .tags import tool_tag_manager
+
+
+__all__ = ["tool_tag_manager"]
diff --git a/lib/galaxy/tools/toolbox/tags.py b/lib/galaxy/tools/toolbox/tags.py
new file mode 100644
index 00000000000..a8aa6c53360
--- /dev/null
+++ b/lib/galaxy/tools/toolbox/tags.py
@@ -0,0 +1,76 @@
+""" Handle details of tool tagging - perhaps a deprecated feature.
+"""
+from abc import ABCMeta
+from abc import abstractmethod
+
+import logging
+log = logging.getLogger( __name__ )
+
+
+def tool_tag_manager( app ):
+ """ Build a tool tag manager according to app's configuration
+ and return it.
+ """
+ if hasattr( app.config, "get_bool" ) and app.config.get_bool( 'enable_tool_tags', False ):
+ return PersistentToolTagManager( app )
+ else:
+ return NullToolTagManager()
+
+
+class AbstractToolTagManager( object ):
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def reset_tags( self ):
+ """ Starting to load tool panels, reset all tags.
+ """
+
+ @abstractmethod
+ def handle_tags( self, tool_id, tool_definition_source ):
+ """ Parse out tags and persist them.
+ """
+
+
+class NullToolTagManager( AbstractToolTagManager ):
+
+ def reset_tags( self ):
+ return None
+
+ def handle_tags( self, tool_id, tool_definition_source ):
+ return None
+
+
+class PersistentToolTagManager( AbstractToolTagManager ):
+
+ def __init__( self, app ):
+ self.app = app
+ self.sa_session = app.sa_session
+
+ def reset_tags( self ):
+ log.info("removing all tool tag associations (" + str( self.sa_session.query( self.app.model.ToolTagAssociation ).count() ) + ")" )
+ self.sa_session.query( self.app.model.ToolTagAssociation ).delete()
+ self.sa_session.flush()
+
+ def handle_tags( self, tool_id, tool_definition_source ):
+ elem = tool_definition_source
+ if self.app.config.get_bool( 'enable_tool_tags', False ):
+ tag_names = elem.get( "tags", "" ).split( "," )
+ for tag_name in tag_names:
+ if tag_name == '':
+ continue
+ tag = self.sa_session.query( self.app.model.Tag ).filter_by( name=tag_name ).first()
+ if not tag:
+ tag = self.app.model.Tag( name=tag_name )
+ self.sa_session.add( tag )
+ self.sa_session.flush()
+ tta = self.app.model.ToolTagAssociation( tool_id=tool_id, tag_id=tag.id )
+ self.sa_session.add( tta )
+ self.sa_session.flush()
+ else:
+ for tagged_tool in tag.tagged_tools:
+ if tagged_tool.tool_id == tool_id:
+ break
+ else:
+ tta = self.app.model.ToolTagAssociation( tool_id=tool_id, tag_id=tag.id )
+ self.sa_session.add( tta )
+ self.sa_session.flush()
From dee0793e5a0d5955cdf7da26d694faed04b818b0 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:28 -0500
Subject: [PATCH 024/473] Tweak completely broken
DataTransfer._execute_workflow to respect toolbox abstraction. Least of the
brokeness at this point I suppose - but I would like to not expose
tools_by_id external to the Toolbox.
---
lib/galaxy/jobs/deferred/data_transfer.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/galaxy/jobs/deferred/data_transfer.py b/lib/galaxy/jobs/deferred/data_transfer.py
index f1b4969e707..4589b2f7117 100644
--- a/lib/galaxy/jobs/deferred/data_transfer.py
+++ b/lib/galaxy/jobs/deferred/data_transfer.py
@@ -356,7 +356,7 @@ class DataTransfer( object ):
for i, step in enumerate( workflow.steps ):
job = None
if step.type == 'tool' or step.type is None:
- tool = self.app.toolbox.tools_by_id[ step.tool_id ]
+ tool = self.app.toolbox.get_tool( step.tool_id )
def callback( input, value, prefixed_name, prefixed_label ):
if isinstance( input, DataToolParameter ):
if prefixed_name in step.input_connections_by_name:
From fb1e9fc5f40065c5ca7398e13d2c60d5e405fc85 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:30 -0500
Subject: [PATCH 025/473] Start decoupling Toolbox elements from XML
(sections+labels). Just a small tweak and some tests to verify these things
can indeed be created from dictionaries. Also slightly simplifies
galaxy.tools.
---
lib/galaxy/tools/__init__.py | 18 ++++++++++++------
test/unit/tools/test_tool_panel.py | 26 ++++++++++++++++++++++++++
2 files changed, 38 insertions(+), 6 deletions(-)
create mode 100644 test/unit/tools/test_tool_panel.py
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index c200e062395..81983802dba 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -330,12 +330,13 @@ class ToolBox( object, Dictifiable ):
elif isinstance( val, ToolSectionLabel ):
self.tool_panel[ key ] = val
elif isinstance( val, ToolSection ):
- elem = ElementTree.Element( 'section' )
- elem.attrib[ 'id' ] = val.id or ''
- elem.attrib[ 'name' ] = val.name or ''
- elem.attrib[ 'version' ] = val.version or ''
- section = ToolSection( elem )
- log.debug( "Loading section: %s" % elem.get( 'name' ) )
+ section_dict = {
+ 'id': val.id or '',
+ 'name': val.name or '',
+ 'version': val.version or '',
+ }
+ section = ToolSection( section_dict )
+ log.debug( "Loading section: %s" % section_dict.get( 'name' ) )
for section_key, section_val in val.elems.items():
if isinstance( section_val, Tool ):
tool_id = section_key.replace( 'tool_', '', 1 )
@@ -1050,6 +1051,8 @@ class ToolSection( object, Dictifiable ):
dict_collection_visible_keys = ( 'id', 'name', 'version' )
def __init__( self, elem=None ):
+ """ Build a ToolSection from an ElementTree element or a dictionary.
+ """
f = lambda elem, val: elem is not None and elem.get( val ) or ''
self.name = f( elem, 'name' )
self.id = f( elem, 'id' )
@@ -1089,6 +1092,9 @@ class ToolSectionLabel( object, Dictifiable ):
dict_collection_visible_keys = ( 'id', 'text', 'version' )
def __init__( self, elem ):
+ """ Build a ToolSectionLabel from an ElementTree element or a
+ dictionary.
+ """
self.text = elem.get( "text" )
self.id = elem.get( "id" )
self.version = elem.get( "version" ) or ''
diff --git a/test/unit/tools/test_tool_panel.py b/test/unit/tools/test_tool_panel.py
new file mode 100644
index 00000000000..33831f9ded7
--- /dev/null
+++ b/test/unit/tools/test_tool_panel.py
@@ -0,0 +1,26 @@
+from xml.etree import ElementTree as ET
+from galaxy.tools import ToolSection
+
+
+def test_tool_section( ):
+ elem = ET.Element( 'section' )
+ elem.attrib[ 'name' ] = "Cool Tools"
+ elem.attrib[ 'id' ] = "cool1"
+
+ section = ToolSection( elem )
+ assert section.id == "cool1"
+ assert section.name == "Cool Tools"
+ assert section.version == ""
+
+ section = ToolSection( dict(
+ id="cool1",
+ name="Cool Tools"
+ ) )
+ assert section.id == "cool1"
+ assert section.name == "Cool Tools"
+ assert section.version == ""
+
+ section = ToolSection()
+ assert section.id == ""
+ assert section.name == ""
+ assert section.version == ""
From 8a10d14c3260f7b369acdd7e30c6d81a4bafa6d1 Mon Sep 17 00:00:00 2001
From: John Chilton
Date: Mon, 22 Dec 2014 13:16:30 -0500
Subject: [PATCH 026/473] Add comment to auto-generated
integrated_tool_panel.xml.
Someone more familiar with integrated_tool_panel.xml should feel free to make this comment sharper - but something is better nothing I think.
---
lib/galaxy/tools/__init__.py | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index 81983802dba..8b1a2af38d8 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -96,6 +96,16 @@ JOB_RESOURCE_CONDITIONAL_XML = """
HELP_UNINITIALIZED = threading.Lock()
+INTEGRATED_TOOL_PANEL_DESCRIPTION = """
+This is Galaxy's integrated tool panel and probably should not be modified
+directly. It will be regenerated each time Galaxy starts up. To modify locally
+managed tools (e.g. from tool_conf.xml) modify that file directly and restart
+Galaxy. Whenever possible Tool Shed managed tools (e.g. from shed_tool_conf.xml)
+should be managed from within the Galaxy interface of via is UI - but if changes
+are nessecary (such as to hide a tool or re-assign its section) modify that file
+and restart Galaxy.
+"""
+
class ToolNotFoundException( Exception ):
pass
@@ -396,6 +406,9 @@ class ToolBox( object, Dictifiable ):
fd, filename = tempfile.mkstemp()
os.write( fd, '\n' )
os.write( fd, '\n' )
+ os.write( fd, ' \n')
for key, item in self.integrated_tool_panel.items():
if item:
if isinstance( item, Tool ):
From 981954e1077c82ff459c3748517a5ea1c72b7407 Mon Sep 17 00:00:00 2001
From: Carl Eberhard
Date: Mon, 22 Dec 2014 14:35:08 -0500
Subject: [PATCH 027/473] UsesHistoryMixin, get_history: allow anon users to
get their current history; update browser tests; remove unused selenium tests
---
lib/galaxy/web/base/controller.py | 5 +
test/casperjs/anon-history-tests.js | 198 +++---
.../api-anon-history-permission-tests.js | 327 +++++----
test/casperjs/api-anon-history-tests.js | 181 +++--
test/casperjs/api-configuration-tests.js | 91 ++-
test/casperjs/api-hda-tests.js | 664 +++++++++---------
test/casperjs/api-history-permission-tests.js | 445 ++++++------
test/casperjs/api-history-tests.js | 2 +-
test/casperjs/api-tool-tests.js | 2 +-
test/casperjs/api-user-tests.js | 68 +-
test/casperjs/api-visualizations-tests.js | 336 ++++-----
test/casperjs/api-workflow-tests.js | 440 ++++++------
test/casperjs/hda-state-tests.js | 658 +++++++++--------
test/casperjs/history-options-tests.js | 166 ++---
test/casperjs/history-panel-tests.js | 451 ++++++------
test/casperjs/modules/api.js | 20 +-
test/casperjs/modules/historyoptions.js | 53 +-
test/casperjs/upload-tests.js | 101 +--
test/selenium/root/Login.html | 52 --
test/selenium/root/Suite.html | 15 -
test/selenium/root/UserRegistration.html | 137 ----
test/selenium/visualization/Trackster.html | 62 --
.../workflow/CreateSimpleWorkflow.html | 188 -----
test/selenium/workflow/CreateUser.html | 67 --
...kflowWithGroupingAndRuntimeParameters.html | 217 ------
...kflowWithGroupingAndRuntimeParameters.html | 92 ---
test/selenium/workflow/Suite.html | 18 -
.../workflow/VerifyRunSimpleWorkflow.html | 162 -----
28 files changed, 2018 insertions(+), 3200 deletions(-)
delete mode 100644 test/selenium/root/Login.html
delete mode 100644 test/selenium/root/Suite.html
delete mode 100644 test/selenium/root/UserRegistration.html
delete mode 100644 test/selenium/visualization/Trackster.html
delete mode 100644 test/selenium/workflow/CreateSimpleWorkflow.html
delete mode 100644 test/selenium/workflow/CreateUser.html
delete mode 100644 test/selenium/workflow/CreateWorkflowWithGroupingAndRuntimeParameters.html
delete mode 100644 test/selenium/workflow/RunWorkflowWithGroupingAndRuntimeParameters.html
delete mode 100644 test/selenium/workflow/Suite.html
delete mode 100644 test/selenium/workflow/VerifyRunSimpleWorkflow.html
diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py
index 7567daa6d5d..08df7a61031 100644
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -255,6 +255,11 @@ class UsesHistoryMixin( SharableItemSecurityMixin ):
"""
Get a History from the database by id, verifying ownership.
"""
+ if trans.user is None and trans.history:
+ if id == trans.security.encode_id( trans.history.id ):
+ return trans.history
+ raise ItemOwnershipException( "Must be logged in to manage Galaxy items", type='error' )
+
history = self.get_object( trans, id, 'History',
check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted )
history = self.security_check( trans, history, check_ownership, check_accessible )
diff --git a/test/casperjs/anon-history-tests.js b/test/casperjs/anon-history-tests.js
index 1b9c1b8b01e..08d9908abc2 100644
--- a/test/casperjs/anon-history-tests.js
+++ b/test/casperjs/anon-history-tests.js
@@ -7,117 +7,111 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Testing histories for anonymous users', 0, function suite( test ){
spaceghost.start();
-// ===================================================================
+ // ===================================================================
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+ spaceghost.info( 'Will use fixtureData.testUser: ' + email );
+ }
-var email = spaceghost.user.getRandomEmail(),
- password = '123456';
-if( spaceghost.fixtureData.testUser ){
- email = spaceghost.fixtureData.testUser.email;
- password = spaceghost.fixtureData.testUser.password;
- spaceghost.info( 'Will use fixtureData.testUser: ' + email );
-}
+ var editableTextClass = spaceghost.data.selectors.editableText,
+ editableTextInput = spaceghost.data.selectors.editableTextInput,
-var tooltipSelector = spaceghost.data.selectors.tooltipBalloon,
- editableTextClass = spaceghost.data.selectors.editableText,
- editableTextInput = spaceghost.data.selectors.editableTextInput,
+ unnamedName = spaceghost.historypanel.data.text.history.newName,
+ nameSelector = spaceghost.historypanel.data.selectors.history.name,
+ sizeSelector = spaceghost.historypanel.data.selectors.history.size,
+ initialSizeStr = spaceghost.historypanel.data.text.history.newSize,
+ tagIconSelector = spaceghost.historypanel.data.selectors.history.tagIcon,
+ annoIconSelector = spaceghost.historypanel.data.selectors.history.annoIcon,
+ emptyMsgSelector = spaceghost.historypanel.data.selectors.history.emptyMsg,
+ emptyMsgStr = spaceghost.historypanel.data.text.history.emptyMsg;
- unnamedName = spaceghost.historypanel.data.text.history.newName,
- nameSelector = spaceghost.historypanel.data.selectors.history.name,
- sizeSelector = spaceghost.historypanel.data.selectors.history.size,
- initialSizeStr = spaceghost.historypanel.data.text.history.newSize,
- tagIconSelector = spaceghost.historypanel.data.selectors.history.tagIcon,
- annoIconSelector = spaceghost.historypanel.data.selectors.history.annoIcon,
- emptyMsgSelector = spaceghost.historypanel.data.selectors.history.emptyMsg,
- emptyMsgStr = spaceghost.historypanel.data.text.history.emptyMsg,
- anonNameTooltip = spaceghost.historypanel.data.text.anonymous.tooltips.name;
-
-var historyFrameInfo = {},
- filenameToUpload = '1.txt',
- filepathToUpload = '../../test-data/' + filenameToUpload,
- testUploadInfo = {};
+ var filenameToUpload = '1.txt',
+ filepathToUpload = '../../test-data/' + filenameToUpload;
-// =================================================================== TESTS
-// ------------------------------------------------------------------- check the anonymous new, history for form
-spaceghost.openHomePage().historypanel.waitForHdas( function testPanelStructure(){
- this.test.comment( 'history panel for anonymous user, new history' );
+ // ------------------------------------------------------------------- check the anonymous new, history for form
+ spaceghost.openHomePage().historypanel.waitForHdas( function testPanelStructure(){
+ this.test.comment( 'history panel for anonymous user, new history' );
- this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
- this.test.assertExists( nameSelector, nameSelector + ' exists' );
- this.test.assertVisible( nameSelector, 'History name is visible' );
- this.test.assertSelectorHasText( nameSelector, unnamedName, 'History name is ' + unnamedName );
-
- this.test.comment( "history should display size and size should be 0 bytes" );
- this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
- this.test.assertVisible( sizeSelector, 'History size is visible' );
- this.test.assertSelectorHasText( sizeSelector, initialSizeStr,
- 'History size has "' + initialSizeStr + '"' );
-
- this.test.comment( "NO tags or annotations icons should be available for an anonymous user" );
- this.test.assertDoesntExist( tagIconSelector, 'Tag icon button not found' );
- this.test.assertDoesntExist( annoIconSelector, 'Annotation icon button not found' );
-
- this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
- this.test.comment( "A message about the current history being empty should be displayed" );
- this.test.assertVisible( emptyMsgSelector, 'Empty history message is visible' );
- this.test.assertSelectorHasText( emptyMsgSelector, emptyMsgStr,
- 'Message contains "' + emptyMsgStr + '"' );
-
- this.test.comment( 'name should NOT be editable when clicked by anon-user' );
- this.assertDoesntHaveClass( nameSelector, editableTextClass, "Name field is not classed as editable text" );
- this.click( nameSelector );
- this.test.assertDoesntExist( editableTextInput, "Clicking on name does not create an input" );
-});
-
-// ------------------------------------------------------------------- anon user can upload file
-spaceghost.then( function testAnonUpload(){
- this.test.comment( 'anon-user should be able to upload files' );
-
- spaceghost.tools.uploadFile( filepathToUpload, function uploadCallback( _uploadInfo ){
- this.debug( 'uploaded HDA info: ' + this.jsonStr( this.quickInfo( _uploadInfo.hdaElement ) ) );
- var hasHda = _uploadInfo.hdaElement,
- hasClass = _uploadInfo.hdaElement.attributes[ 'class' ],
- hasOkClass = _uploadInfo.hdaElement.attributes[ 'class' ].indexOf( 'state-ok' ) !== -1;
- this.test.assert( ( hasHda && hasClass && hasOkClass ), "Uploaded file: " + _uploadInfo.hdaElement.text );
- testUploadInfo = _uploadInfo;
- });
-
-});
-spaceghost.then( function testAnonUpload(){
- this.test.comment( "empty should be NO LONGER be displayed" );
- this.test.assertNotVisible( emptyMsgSelector, 'Empty history message is not visible' );
-});
-
-// ------------------------------------------------------------------- anon user can run tool on file
-
-// ------------------------------------------------------------------- anon user registers/logs in -> same history
-spaceghost.user.loginOrRegisterUser( email, password ).openHomePage( function(){
- this.test.comment( 'anon-user should login and be associated with previous history' );
-
- var loggedInAs = spaceghost.user.loggedInAs();
- this.test.assert( loggedInAs === email, 'loggedInAs() matches email: "' + loggedInAs + '"' );
-
- this.historypanel.waitForHdas( function(){
- var hdaInfo = this.historypanel.hdaElementInfoByTitle( filenameToUpload );
- this.test.assert( hdaInfo !== null, "After logging in - found a matching hda by name and hid" );
- if( hdaInfo ){
- this.test.assert( testUploadInfo.hdaElement.attributes.id === hdaInfo.attributes.id,
- "After logging in - found a matching hda by hda view id: " + hdaInfo.attributes.id );
- }
- });
-});
-
-// ------------------------------------------------------------------- logs out -> new history
-spaceghost.user.logout().openHomePage( function(){
- this.test.comment( 'logging out should create a new, anonymous history' );
-
- this.historypanel.waitForHdas( function(){
+ this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
+ this.test.assertExists( nameSelector, nameSelector + ' exists' );
+ this.test.assertVisible( nameSelector, 'History name is visible' );
this.test.assertSelectorHasText( nameSelector, unnamedName, 'History name is ' + unnamedName );
+
+ this.test.comment( "history should display size and size should be 0 bytes" );
+ this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
+ this.test.assertVisible( sizeSelector, 'History size is visible' );
+ this.test.assertSelectorHasText( sizeSelector, initialSizeStr,
+ 'History size has "' + initialSizeStr + '"' );
+
+ this.test.comment( "NO tags or annotations icons should be available for an anonymous user" );
+ this.test.assertDoesntExist( tagIconSelector, 'Tag icon button not found' );
+ this.test.assertDoesntExist( annoIconSelector, 'Annotation icon button not found' );
+
+ this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
+ this.test.comment( "A message about the current history being empty should be displayed" );
+ this.test.assertVisible( emptyMsgSelector, 'Empty history message is visible' );
this.test.assertSelectorHasText( emptyMsgSelector, emptyMsgStr,
'Message contains "' + emptyMsgStr + '"' );
- });
-});
-// ===================================================================
+ this.test.comment( 'name should NOT be editable when clicked by anon-user' );
+ this.assertDoesntHaveClass( nameSelector, editableTextClass, "Name field is not classed as editable text" );
+ this.click( nameSelector );
+ this.test.assertDoesntExist( editableTextInput, "Clicking on name does not create an input" );
+ });
+
+ // ------------------------------------------------------------------- anon user can upload file
+ var uploadedId = null;
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: filepathToUpload }, function( id, json ){
+ uploadedId = id;
+ });
+ spaceghost.openHomePage().historypanel.waitForHdas( function testAnonUpload(){
+ this.test.comment( 'anon-user should be able to upload files' );
+ this.test.assertExists( '#dataset-' + uploadedId, "found hda" );
+ var hdaElement = this.elementInfoOrNull( '#dataset-' + uploadedId );
+ this.debug( 'hdaElement: ' + hdaElement );
+ if( hdaElement ){
+ this.test.assert( hdaElement.attributes[ 'class' ].indexOf( 'state-ok' ) !== -1,
+ "Uploaded file: " + hdaElement.text );
+ }
+
+ this.test.comment( "empty should be NO LONGER be displayed" );
+ this.test.assertNotVisible( emptyMsgSelector, 'Empty history message is not visible' );
+ });
+
+ // ------------------------------------------------------------------- anon user can run tool on file
+
+ // ------------------------------------------------------------------- anon user registers/logs in -> same history
+ spaceghost.user.loginOrRegisterUser( email, password ).openHomePage( function(){
+ this.test.comment( 'anon-user should login and be associated with previous history' );
+
+ var loggedInAs = spaceghost.user.loggedInAs();
+ this.test.assert( loggedInAs === email, 'loggedInAs() matches email: "' + loggedInAs + '"' );
+
+ this.historypanel.waitForHdas( function(){
+ var hdaInfo = this.historypanel.hdaElementInfoByTitle( filenameToUpload );
+ this.test.assert( hdaInfo !== null, "After logging in - found a matching hda by name and hid" );
+ if( hdaInfo ){
+ this.test.assert( 'dataset-' + uploadedId === hdaInfo.attributes.id,
+ "After logging in - found a matching hda by hda view id: " + hdaInfo.attributes.id );
+ }
+ });
+ });
+
+ // ------------------------------------------------------------------- logs out -> new history
+ spaceghost.user.logout().openHomePage( function(){
+ this.test.comment( 'logging out should create a new, anonymous history' );
+
+ this.historypanel.waitForHdas( function(){
+ this.test.assertSelectorHasText( nameSelector, unnamedName, 'History name is ' + unnamedName );
+ this.test.assertSelectorHasText( emptyMsgSelector, emptyMsgStr,
+ 'Message contains "' + emptyMsgStr + '"' );
+ });
+ });
+
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-anon-history-permission-tests.js b/test/casperjs/api-anon-history-permission-tests.js
index f63cdabb650..dba00b98361 100644
--- a/test/casperjs/api-anon-history-permission-tests.js
+++ b/test/casperjs/api-anon-history-permission-tests.js
@@ -8,195 +8,194 @@ spaceghost.test.begin( 'Test permissions for accessible, published, and inaccess
+ 'with anonymous users over the API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-var email = spaceghost.user.getRandomEmail(),
- password = '123456';
-if( spaceghost.fixtureData.testUser ){
- email = spaceghost.fixtureData.testUser.email;
- password = spaceghost.fixtureData.testUser.password;
-}
-var inaccessibleHistory, accessibleHistory, publishedHistory,
- inaccessibleHdas, accessibleHdas, publishedHdas,
- accessibleLink;
+ // =================================================================== SET UP
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+ }
+ var inaccessibleHistory, accessibleHistory, publishedHistory,
+ inaccessibleHdas, accessibleHdas, publishedHdas;
-//// ------------------------------------------------------------------------------------------- create 3 histories
-spaceghost.user.loginOrRegisterUser( email, password );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- // create three histories: make the 2nd importable (via the API), and the third published
+ //// ------------------------------------------------------------------------------------------- create 3 histories
+ spaceghost.user.loginOrRegisterUser( email, password );
+ spaceghost.openHomePage().then( function(){
+ // create three histories: make the 2nd importable (via the API), and the third published
- // make the current the inaccessible one
- inaccessibleHistory = this.api.histories.index()[0];
- this.api.histories.update( inaccessibleHistory.id, { name: 'inaccessible' });
- inaccessibleHistory = this.api.histories.index()[0];
+ // make the current the inaccessible one
+ inaccessibleHistory = this.api.histories.index()[0];
+ this.api.histories.update( inaccessibleHistory.id, { name: 'inaccessible' });
+ inaccessibleHistory = this.api.histories.index()[0];
+
+ accessibleHistory = this.api.histories.create({ name: 'accessible' });
+ var returned = this.api.histories.update( accessibleHistory.id, {
+ importable : true
+ });
+ //this.debug( this.jsonStr( returned ) );
+ accessibleHistory = this.api.histories.show( accessibleHistory.id );
+
+ publishedHistory = this.api.histories.create({ name: 'published' });
+ returned = this.api.histories.update( publishedHistory.id, {
+ published : true
+ });
+ //this.debug( this.jsonStr( returned ) );
+ publishedHistory = this.api.histories.show( publishedHistory.id );
- accessibleHistory = this.api.histories.create({ name: 'accessible' });
- var returned = this.api.histories.update( accessibleHistory.id, {
- importable : true
});
- //this.debug( this.jsonStr( returned ) );
- accessibleHistory = this.api.histories.show( accessibleHistory.id );
- publishedHistory = this.api.histories.create({ name: 'published' });
- returned = this.api.histories.update( publishedHistory.id, {
- published : true
+ //// ------------------------------------------------------------------------------------------- upload some files
+ spaceghost.then( function(){
+ this.api.tools.thenUpload( inaccessibleHistory.id, { filepath: '../../test-data/1.bed' });
+ this.api.tools.thenUpload( accessibleHistory.id, { filepath: '../../test-data/1.bed' });
+ this.api.tools.thenUpload( publishedHistory.id, { filepath: '../../test-data/1.bed' });
});
- //this.debug( this.jsonStr( returned ) );
- publishedHistory = this.api.histories.show( publishedHistory.id );
-
-});
-
-//// ------------------------------------------------------------------------------------------- upload some files
-spaceghost.then( function(){
- this.api.tools.thenUpload( inaccessibleHistory.id, { filepath: '../../test-data/1.bed' });
- this.api.tools.thenUpload( accessibleHistory.id, { filepath: '../../test-data/1.bed' });
- this.api.tools.thenUpload( publishedHistory.id, { filepath: '../../test-data/1.bed' });
-});
-spaceghost.then( function(){
- // check that they're there
- inaccessibleHdas = this.api.hdas.index( inaccessibleHistory.id ),
- accessibleHdas = this.api.hdas.index( accessibleHistory.id ),
- publishedHdas = this.api.hdas.index( publishedHistory.id );
-});
-spaceghost.user.logout();
-
-// =================================================================== TESTS
-//// ------------------------------------------------------------------------------------------- anon user
-function testAnonReadFunctionsOnAccessible( history, hdas ){
- this.test.comment( '---- testing read/accessibility functions for ACCESSIBLE history: ' + history.name );
-
- // read functions for history
- this.test.comment( 'show should work for history: ' + history.name );
- this.test.assert( this.api.histories.show( history.id ).id === history.id,
- 'show worked' );
- this.test.comment( 'copying should fail for history (multiple histories not allowed): ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.create({ history_id : history.id });
- }, 403, 'API authentication required for this request', 'update authentication required' );
-
- // read functions for history contents
- this.test.comment( 'index of history contents should work for history: ' + history.name );
- this.test.assert( this.api.hdas.index( history.id ).length === 1,
- 'hda index worked' );
- this.test.comment( 'showing of history contents should work for history: ' + history.name );
- this.test.assert( this.api.hdas.show( history.id, hdas[0].id ).id === hdas[0].id,
- 'hda show worked' );
-
- this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
- + ' should work from accessible history: ' + history.name );
- this.api.hdas.create( this.api.histories.index()[0].id, {
- source : 'hda',
- content : hdas[0].id
+ spaceghost.then( function(){
+ // check that they're there
+ inaccessibleHdas = this.api.hdas.index( inaccessibleHistory.id ),
+ accessibleHdas = this.api.hdas.index( accessibleHistory.id ),
+ publishedHdas = this.api.hdas.index( publishedHistory.id );
});
-}
+ spaceghost.user.logout();
-function testAnonReadFunctionsOnInaccessible( history, hdas ){
- this.test.comment( '---- testing read/accessibility functions for INACCESSIBLE history: ' + history.name );
+ // =================================================================== TESTS
+ //// ------------------------------------------------------------------------------------------- anon user
+ function testAnonReadFunctionsOnAccessible( history, hdas ){
+ this.test.comment( '---- testing read/accessibility functions for ACCESSIBLE history: ' + history.name );
- // read functions for history
- this.test.comment( 'show should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.show( history.id );
- }, 403, 'History is not accessible to the current user', 'show failed with error' );
- this.test.comment( 'copying should fail for history (implicit multiple histories): ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.create({ history_id : history.id });
- }, 403, 'API authentication required for this request', 'copy failed with error' );
+ // read functions for history
+ this.test.comment( 'show should work for history: ' + history.name );
+ this.test.assert( this.api.histories.show( history.id ).id === history.id,
+ 'show worked' );
+ this.test.comment( 'copying should fail for history (multiple histories not allowed): ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.create({ history_id : history.id });
+ }, 403, 'API authentication required for this request', 'update authentication required' );
- // read functions for history contents
- this.test.comment( 'index and show of history contents should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.index( history.id );
- }, 403, 'History is not accessible to the current user', 'hda index failed with error' );
- this.api.assertRaises( function(){
- this.api.hdas.show( history.id, hdas[0].id );
- }, 403, 'History is not accessible to the current user', 'hda show failed with error' );
+ // read functions for history contents
+ this.test.comment( 'index of history contents should work for history: ' + history.name );
+ this.test.assert( this.api.hdas.index( history.id ).length === 1,
+ 'hda index worked' );
+ this.test.comment( 'showing of history contents should work for history: ' + history.name );
+ this.test.assert( this.api.hdas.show( history.id, hdas[0].id ).id === hdas[0].id,
+ 'hda show worked' );
- this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
- + ' from an inaccessible history should fail for: ' + history.name );
- this.api.assertRaises( function(){
- var returned = this.api.hdas.create( this.api.histories.index()[0].id, {
+ this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
+ + ' should work from accessible history: ' + history.name );
+ this.api.hdas.create( this.api.histories.index()[0].id, {
source : 'hda',
content : hdas[0].id
});
- this.debug( this.jsonStr( returned ) );
- }, 403, 'History is not accessible to the current user', 'hda copy from failed with error' );
+ }
-}
+ function testAnonReadFunctionsOnInaccessible( history, hdas ){
+ this.test.comment( '---- testing read/accessibility functions for INACCESSIBLE history: ' + history.name );
-function testAnonWriteFunctions( history, hdas ){
- this.test.comment( '---- testing write/ownership functions for history: ' + history.name );
+ // read functions for history
+ this.test.comment( 'show should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.show( history.id );
+ }, 403, 'History is not accessible to the current user', 'show failed with error' );
+ this.test.comment( 'copying should fail for history (implicit multiple histories): ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.create({ history_id : history.id });
+ }, 403, 'API authentication required for this request', 'copy failed with error' );
- this.test.comment( 'update should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.update( history.id, { deleted: true });
- }, 403, 'API authentication required for this request', 'update authentication required' );
- this.test.comment( 'delete should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.delete_( history.id );
- }, 403, 'API authentication required for this request', 'delete authentication required' );
+ // read functions for history contents
+ this.test.comment( 'index and show of history contents should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.index( history.id );
+ }, 403, 'History is not accessible to the current user', 'hda index failed with error' );
+ this.api.assertRaises( function(){
+ this.api.hdas.show( history.id, hdas[0].id );
+ }, 403, 'History is not accessible to the current user', 'hda show failed with error' );
- this.test.comment( 'hda updating should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.update( history.id, hdas[0].id, { deleted: true });
- // anon hda update fails w/ this msg if trying to update non-current history hda
- }, 403, 'You must be logged in to update this history', 'hda update failed with error' );
- this.test.comment( 'hda deletion should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.delete_( history.id, hdas[0].id );
- }, 403, 'API authentication required for this request', 'hda delete failed with error' );
+ this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
+ + ' from an inaccessible history should fail for: ' + history.name );
+ this.api.assertRaises( function(){
+ var returned = this.api.hdas.create( this.api.histories.index()[0].id, {
+ source : 'hda',
+ content : hdas[0].id
+ });
+ this.debug( this.jsonStr( returned ) );
+ }, 403, 'History is not accessible to the current user', 'hda copy from failed with error' );
- this.test.comment( 'copying hda into history should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.create( history.id, {
- source : 'hda',
- // should error before it checks the id
- content : 'bler'
+ }
+
+ function testAnonWriteFunctions( history, hdas ){
+ this.test.comment( '---- testing write/ownership functions for history: ' + history.name );
+
+ this.test.comment( 'update should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.update( history.id, { deleted: true });
+ }, 403, 'API authentication required for this request', 'update authentication required' );
+ this.test.comment( 'delete should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.delete_( history.id );
+ }, 403, 'API authentication required for this request', 'delete authentication required' );
+
+ this.test.comment( 'hda updating should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.update( history.id, hdas[0].id, { deleted: true });
+ // anon hda update fails w/ this msg if trying to update non-current history hda
+ }, 403, 'You must be logged in to update this history', 'hda update failed with error' );
+ this.test.comment( 'hda deletion should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.delete_( history.id, hdas[0].id );
+ }, 403, 'API authentication required for this request', 'hda delete failed with error' );
+
+ this.test.comment( 'copying hda into history should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.create( history.id, {
+ source : 'hda',
+ // should error before it checks the id
+ content : 'bler'
+ });
+ }, 403, 'Must be logged in to manage Galaxy histories', 'hda copy to failed' );
+ }
+
+ function testAnonInaccessible( history, hdas ){
+ testAnonReadFunctionsOnInaccessible.call( this, history, hdas );
+ testAnonWriteFunctions.call( this, history, hdas );
+ }
+
+ function testAnonAccessible( history, hdas ){
+ testAnonReadFunctionsOnAccessible.call( this, history, hdas );
+ testAnonWriteFunctions.call( this, history, hdas );
+ }
+
+ spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+ testAnonInaccessible.call( spaceghost, inaccessibleHistory, inaccessibleHdas );
+ testAnonAccessible.call( spaceghost, accessibleHistory, accessibleHdas );
+ testAnonAccessible.call( spaceghost, publishedHistory, publishedHdas );
+ });
+
+
+ // ------------------------------------------------------------------------------------------- user1 revoke perms
+ spaceghost.user.loginOrRegisterUser( email, password );
+ spaceghost.openHomePage().then( function(){
+ this.test.comment( 'revoking perms should prevent access' );
+ this.api.histories.update( accessibleHistory.id, {
+ importable : false
});
- }, 403, 'Must be logged in to manage Galaxy histories', 'hda copy to failed' );
-}
+ var returned = this.api.histories.show( accessibleHistory.id );
-function testAnonInaccessible( history, hdas ){
- testAnonReadFunctionsOnInaccessible.call( this, history, hdas );
- testAnonWriteFunctions.call( this, history, hdas );
-}
-
-function testAnonAccessible( history, hdas ){
- testAnonReadFunctionsOnAccessible.call( this, history, hdas );
- testAnonWriteFunctions.call( this, history, hdas );
-}
-
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- testAnonInaccessible.call( spaceghost, inaccessibleHistory, inaccessibleHdas );
- testAnonAccessible.call( spaceghost, accessibleHistory, accessibleHdas );
- testAnonAccessible.call( spaceghost, publishedHistory, publishedHdas );
-});
-
-
-// ------------------------------------------------------------------------------------------- user1 revoke perms
-spaceghost.user.loginOrRegisterUser( email, password );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- this.test.comment( 'revoking perms should prevent access' );
- this.api.histories.update( accessibleHistory.id, {
- importable : false
+ this.api.histories.update( publishedHistory.id, {
+ importable : false,
+ published : false
+ });
+ returned = this.api.histories.show( publishedHistory.id );
});
- var returned = this.api.histories.show( accessibleHistory.id );
+ spaceghost.user.logout();
- this.api.histories.update( publishedHistory.id, {
- importable : false,
- published : false
+
+ // ------------------------------------------------------------------------------------------- anon retry perms
+ spaceghost.openHomePage().then( function(){
+ testAnonInaccessible.call( spaceghost, accessibleHistory, accessibleHdas );
+ testAnonInaccessible.call( spaceghost, publishedHistory, publishedHdas );
});
- returned = this.api.histories.show( publishedHistory.id );
-});
-spaceghost.user.logout();
-// ------------------------------------------------------------------------------------------- anon retry perms
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- testAnonInaccessible.call( spaceghost, accessibleHistory, accessibleHdas );
- testAnonInaccessible.call( spaceghost, publishedHistory, publishedHdas );
-});
-
-
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-anon-history-tests.js b/test/casperjs/api-anon-history-tests.js
index edd12453210..33ec5de6752 100644
--- a/test/casperjs/api-anon-history-tests.js
+++ b/test/casperjs/api-anon-history-tests.js
@@ -7,123 +7,116 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test API functions for histories with an anonymous user', 0, function suite( test ){
spaceghost.start();
-// =================================================================== TESTS
-spaceghost.thenOpen( spaceghost.baseUrl ).waitForSelector( spaceghost.historypanel.data.selectors.history.name );
-spaceghost.then( function(){
+ // =================================================================== TESTS
+ spaceghost.thenOpen( spaceghost.baseUrl ).waitForSelector( spaceghost.historypanel.data.selectors.history.name );
+ spaceghost.then( function(){
- // ------------------------------------------------------------------------------------------- anon allowed
- this.test.comment( 'index should get a list of histories' );
- var index = this.api.histories.index();
- this.test.assert( utils.isArray( index ), "index returned an array: length " + index.length );
- this.test.assert( index.length === 1, 'Has at least one history' );
+ // ------------------------------------------------------------------------------------------- anon allowed
+ this.test.comment( 'index should get a list of histories' );
+ var index = this.api.histories.index();
+ this.test.assert( utils.isArray( index ), "index returned an array: length " + index.length );
+ this.test.assert( index.length === 1, 'Has at least one history' );
- this.test.comment( 'show should get a history details object' );
- var historyShow = this.api.histories.show( index[0].id );
- //this.debug( this.jsonStr( historyShow ) );
- this.test.assert( historyShow.id === index[0].id, 'Is the first history' );
- this.test.assert( this.hasKeys( historyShow, [ 'id', 'name', 'user_id' ] ) );
+ this.test.comment( 'show should get a history details object' );
+ var historyShow = this.api.histories.show( index[0].id );
+ //this.debug( this.jsonStr( historyShow ) );
+ this.test.assert( historyShow.id === index[0].id, 'Is the first history' );
+ this.test.assert( this.hasKeys( historyShow, [ 'id', 'name', 'user_id' ] ) );
- // ------------------------------------------------------------------------------------------- anon forbidden
- //TODO: why not return the current history?
- this.test.comment( 'calling show with "most_recently_used" should return None for an anon user' );
- var recent = this.api.histories.show( 'most_recently_used' );
- this.test.assert( recent === null, 'most_recently_used returned None' );
+ // ------------------------------------------------------------------------------------------- anon forbidden
+ //TODO: why not return the current history?
+ this.test.comment( 'calling show with "most_recently_used" should return None for an anon user' );
+ var recent = this.api.histories.show( 'most_recently_used' );
+ this.test.assert( recent === null, 'most_recently_used returned None' );
- this.test.comment( 'Calling create should fail for an anonymous user' );
- this.api.assertRaises( function(){
- this.api.histories.create({ name: 'new' });
- }, 403, 'API authentication required for this request', 'create failed with error' );
+ this.test.comment( 'Calling create should fail for an anonymous user' );
+ this.api.assertRaises( function(){
+ this.api.histories.create({ name: 'new' });
+ }, 403, 'API authentication required for this request', 'create failed with error' );
- this.test.comment( 'Calling delete should fail for an anonymous user' );
- this.api.assertRaises( function(){
- this.api.histories.delete_( historyShow.id );
- }, 403, 'API authentication required for this request', 'create failed with error' );
+ this.test.comment( 'Calling delete should fail for an anonymous user' );
+ this.api.assertRaises( function(){
+ this.api.histories.delete_( historyShow.id );
+ }, 403, 'API authentication required for this request', 'create failed with error' );
- this.test.comment( 'Calling update should fail for an anonymous user' );
- this.api.assertRaises( function(){
- this.api.histories.update( historyShow.id, {} );
- }, 403, 'API authentication required for this request', 'update failed with error' );
+ this.test.comment( 'Calling update should fail for an anonymous user' );
+ this.api.assertRaises( function(){
+ this.api.histories.update( historyShow.id, {} );
+ }, 403, 'API authentication required for this request', 'update failed with error' );
- //TODO: need these two in api.js
- //this.test.comment( 'Calling archive_import should fail for an anonymous user' );
- //this.api.assertRaises( function(){
- // this.api.histories.archive_import( historyShow.id, {} );
- //}, 403, 'API authentication required for this request', 'archive_import failed with error' );
+ //TODO: need these two in api.js
+ //this.test.comment( 'Calling archive_import should fail for an anonymous user' );
+ //this.api.assertRaises( function(){
+ // this.api.histories.archive_import( historyShow.id, {} );
+ //}, 403, 'API authentication required for this request', 'archive_import failed with error' );
- //this.test.comment( 'Calling archive_download should fail for an anonymous user' );
- //this.api.assertRaises( function(){
- // this.api.histories.archive_download( historyShow.id, {} );
- //}, 403, 'API authentication required for this request', 'archive_download failed with error' );
+ //this.test.comment( 'Calling archive_download should fail for an anonymous user' );
+ //this.api.assertRaises( function(){
+ // this.api.histories.archive_download( historyShow.id, {} );
+ //}, 403, 'API authentication required for this request', 'archive_download failed with error' );
- // test server bad id protection
- spaceghost.test.comment( 'A bad id should throw an error' );
- this.api.assertRaises( function(){
- this.api.histories.show( '1234123412341234' );
- }, 400, 'unable to decode', 'Bad Request with invalid id: show' );
+ // test server bad id protection
+ spaceghost.test.comment( 'A bad id should throw an error' );
+ this.api.assertRaises( function(){
+ this.api.histories.show( '1234123412341234' );
+ }, 400, 'unable to decode', 'Bad Request with invalid id: show' );
-});
-
-// ------------------------------------------------------------------------------------------- hdas
-spaceghost.thenOpen( spaceghost.baseUrl ).waitForSelector( spaceghost.historypanel.data.selectors.history.name );
-spaceghost.then( function(){
- spaceghost.tools.uploadFile( '../../test-data/1.sam', function( uploadInfo ){
- this.test.assert( uploadInfo.hdaElement !== null, "Convenience function produced hda" );
- var state = this.historypanel.getHdaState( '#' + uploadInfo.hdaElement.attributes.id );
- this.test.assert( state === 'ok', "Convenience function produced hda in ok state" );
});
-});
-spaceghost.then( function(){
- var current = this.api.histories.index()[0];
+ // ------------------------------------------------------------------------------------------- hdas
+ spaceghost.thenOpen( spaceghost.baseUrl ).waitForSelector( spaceghost.historypanel.data.selectors.history.name );
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: '../../test-data/1.sam' });
+ spaceghost.then( function(){
+ var current = this.api.histories.index()[0];
- // ------------------------------------------------------------------------------------------- anon allowed
- this.test.comment( 'anonymous users can index hdas in their current history' );
- var hdaIndex = this.api.hdas.index( current.id );
- this.test.assert( hdaIndex.length === 1, 'indexed hdas' );
+ // ------------------------------------------------------------------------------------------- anon allowed
+ this.test.comment( 'anonymous users can index hdas in their current history' );
+ var hdaIndex = this.api.hdas.index( current.id );
+ this.test.assert( hdaIndex.length === 1, 'indexed hdas' );
- this.test.comment( 'anonymous users can show hdas in their current history' );
- var hda = this.api.hdas.show( current.id, hdaIndex[0].id );
- this.test.assert( this.hasKeys( hda, [ 'id', 'name' ] ), 'showed hda: ' + hda.name );
+ this.test.comment( 'anonymous users can show hdas in their current history' );
+ var hda = this.api.hdas.show( current.id, hdaIndex[0].id );
+ this.test.assert( this.hasKeys( hda, [ 'id', 'name' ] ), 'showed hda: ' + hda.name );
- this.test.comment( 'anonymous users can hide hdas in their current history' );
- var changed = this.api.hdas.update( current.id, hda.id, { visible: false });
- hda = this.api.hdas.show( current.id, hda.id );
- this.test.assert( hda.visible === false, 'successfully hidden' );
+ this.test.comment( 'anonymous users can hide hdas in their current history' );
+ var changed = this.api.hdas.update( current.id, hda.id, { visible: false });
+ hda = this.api.hdas.show( current.id, hda.id );
+ this.test.assert( hda.visible === false, 'successfully hidden' );
- this.test.comment( 'anonymous users can mark their hdas as deleted in their current history' );
- changed = this.api.hdas.update( current.id, hda.id, { deleted: true });
- hda = this.api.hdas.show( current.id, hda.id );
- this.test.assert( hda.deleted, 'successfully deleted' );
+ this.test.comment( 'anonymous users can mark their hdas as deleted in their current history' );
+ changed = this.api.hdas.update( current.id, hda.id, { deleted: true });
+ hda = this.api.hdas.show( current.id, hda.id );
+ this.test.assert( hda.deleted, 'successfully deleted' );
- // ------------------------------------------------------------------------------------------- anon forbidden
- this.test.comment( 'Creating an hda should work for an anonymous user' );
- var returned = this.api.hdas.create( current.id, { source: 'hda', content: hda.id });
- //this.debug( this.jsonStr( returned ) );
- this.test.assert( returned.name === hda.name, 'name matches: ' + returned.name );
- this.test.assert( returned.id !== hda.id, 'new id: ' + returned.id );
+ // ------------------------------------------------------------------------------------------- anon forbidden
+ this.test.comment( 'Creating an hda should work for an anonymous user' );
+ var returned = this.api.hdas.create( current.id, { source: 'hda', content: hda.id });
+ //this.debug( this.jsonStr( returned ) );
+ this.test.assert( returned.name === hda.name, 'name matches: ' + returned.name );
+ this.test.assert( returned.id !== hda.id, 'new id: ' + returned.id );
- //TODO: should be allowed
- this.test.comment( 'Calling hda delete should fail for an anonymous user' );
- this.api.assertRaises( function(){
- this.api.hdas.delete_( current.id, hda.id );
- }, 403, 'API authentication required for this request', 'delete failed with error' );
+ //TODO: should be allowed
+ this.test.comment( 'Calling hda delete should fail for an anonymous user' );
+ this.api.assertRaises( function(){
+ this.api.hdas.delete_( current.id, hda.id );
+ }, 403, 'API authentication required for this request', 'delete failed with error' );
- //TODO: only sharing, tags, annotations should be blocked/prevented
- this.test.comment( 'Calling update with keys other than "visible" or "deleted" should fail silently' );
- this.test.comment( 'Calling update on tags should fail silently' );
- changed = this.api.hdas.update( current.id, hda.id, { tags: [ 'one' ] });
- hda = this.api.hdas.show( current.id, hda.id );
- this.test.assert( hda.tags.length === 0, 'tags were not set: ' + this.jsonStr( hda.tags ) );
+ //TODO: only sharing, tags, annotations should be blocked/prevented
+ this.test.comment( 'Calling update with keys other than "visible" or "deleted" should fail silently' );
+ this.test.comment( 'Calling update on tags should fail silently' );
+ changed = this.api.hdas.update( current.id, hda.id, { tags: [ 'one' ] });
+ hda = this.api.hdas.show( current.id, hda.id );
+ this.test.assert( hda.tags.length === 0, 'tags were not set: ' + this.jsonStr( hda.tags ) );
- this.test.comment( 'Calling update on annotation should fail silently' );
- changed = this.api.hdas.update( current.id, hda.id, { annotation: 'yup yup yup' });
- hda = this.api.hdas.show( current.id, hda.id );
- this.test.assert( !hda.annotation, 'annotation was not set: ' + hda.annotation );
+ this.test.comment( 'Calling update on annotation should fail silently' );
+ changed = this.api.hdas.update( current.id, hda.id, { annotation: 'yup yup yup' });
+ hda = this.api.hdas.show( current.id, hda.id );
+ this.test.assert( !hda.annotation, 'annotation was not set: ' + hda.annotation );
-});
+ });
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-configuration-tests.js b/test/casperjs/api-configuration-tests.js
index eb039382972..faeb301c92a 100644
--- a/test/casperjs/api-configuration-tests.js
+++ b/test/casperjs/api-configuration-tests.js
@@ -7,57 +7,55 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test the Galaxy configuration API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-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 );
+ // =================================================================== SET UP
+ 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 );
-// =================================================================== TESTS
-var normKeys = [
- 'enable_unique_workflow_defaults',
- 'ftp_upload_site',
- 'ftp_upload_dir',
- 'wiki_url',
- 'support_url',
- 'logo_url',
- 'terms_url',
- 'allow_user_dataset_purge'
- ],
- adminKeys = normKeys.concat([
- 'library_import_dir',
- 'user_library_import_dir',
- 'allow_library_path_paste',
- 'allow_user_creation',
- 'allow_user_deletion'
- ]);
+ // =================================================================== TESTS
+ var normKeys = [
+ 'enable_unique_workflow_defaults',
+ 'ftp_upload_site',
+ 'ftp_upload_dir',
+ 'wiki_url',
+ 'support_url',
+ 'logo_url',
+ 'terms_url',
+ 'allow_user_dataset_purge'
+ ],
+ adminKeys = normKeys.concat([
+ 'library_import_dir',
+ 'user_library_import_dir',
+ 'allow_library_path_paste',
+ 'allow_user_creation',
+ 'allow_user_deletion'
+ ]);
+ // ------------------------------------------------------------------------------------------- INDEX
+ spaceghost.openHomePage().then( function(){
+ this.test.comment( 'index should get a (shortened) list of configuration settings '
+ + 'when requested by a normal user' );
-// ------------------------------------------------------------------------------------------- INDEX
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+ var configIndex = this.api.configuration.index();
+ this.debug( this.jsonStr( configIndex ) );
+ this.test.assert( utils.isObject( configIndex ), "index returned an object" );
+ this.test.assert( this.hasKeys( configIndex, normKeys ), 'Has the proper keys' );
- this.test.comment( 'index should get a (shortened) list of configuration settings '
- + 'when requested by a normal user' );
- var configIndex = this.api.configuration.index();
- this.debug( this.jsonStr( configIndex ) );
- this.test.assert( utils.isObject( configIndex ), "index returned an object" );
- this.test.assert( this.hasKeys( configIndex, normKeys ), 'Has the proper keys' );
+ });
+ spaceghost.user.logout();
-});
-spaceghost.user.logout();
+ // ------------------------------------------------------------------------------------------- INDEX (admin)
+ spaceghost.tryStepsCatch( function tryAdminLogin(){
+ spaceghost.user.loginAdmin();
+ }, function(){} );
-// ------------------------------------------------------------------------------------------- INDEX (admin)
-spaceghost.tryStepsCatch( function tryAdminLogin(){
- spaceghost.user.loginAdmin();
-}, function(){} );
-
-//}, function failedLoginRegister(){
-// this.info( 'Admin level configuration API tests not run: no admin account available' );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- spaceghost.waitForMasthead( function() {
+ //}, function failedLoginRegister(){
+ // this.info( 'Admin level configuration API tests not run: no admin account available' );
+ spaceghost.openHomePage().waitForMasthead( function(){
if( spaceghost.user.userIsAdmin() ){
this.test.comment( 'index should get a (full) list of configuration settings '
+ 'when requested by an admin user' );
@@ -70,9 +68,8 @@ spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
this.info( 'Admin level configuration API tests not run: no admin account available' );
}
});
-});
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-hda-tests.js b/test/casperjs/api-hda-tests.js
index dd72dee3a49..155e0cef705 100644
--- a/test/casperjs/api-hda-tests.js
+++ b/test/casperjs/api-hda-tests.js
@@ -7,363 +7,359 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test the HDA API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-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 );
+ // =================================================================== SET UP
+ 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 );
-spaceghost.thenOpen( spaceghost.baseUrl, function(){
- this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
- this.api.tools.thenUpload( spaceghost.api.histories.index()[0].id, {
+ spaceghost.openHomePage().api.tools.thenUploadToCurrent({
filepath: '../../test-data/1.sam'
});
-});
-// =================================================================== TESTS
-var summaryKeys = [ 'id', 'name', 'history_id', 'state', 'deleted', 'purged', 'visible', 'url', 'type' ],
- detailKeys = [
- // the following are always present regardless of datatype
- 'id', 'name', 'api_type', 'model_class',
- 'history_id', 'hid',
- 'accessible', 'deleted', 'visible', 'purged',
- 'state', 'data_type', 'file_ext', 'file_size',
- 'misc_info', 'misc_blurb',
- 'download_url', 'visualizations', 'display_apps', 'display_types',
- 'genome_build',
- // the following are NOT always present DEPENDING ON datatype
- 'metadata_dbkey',
- 'metadata_column_names', 'metadata_column_types', 'metadata_columns',
- 'metadata_comment_lines', 'metadata_data_lines'
- ];
+ // =================================================================== TESTS
+ var summaryKeys = [ 'id', 'name', 'history_id', 'state', 'deleted', 'purged', 'visible', 'url', 'type' ],
+ detailKeys = [
+ // the following are always present regardless of datatype
+ 'id', 'name', 'api_type', 'model_class',
+ 'history_id', 'hid',
+ 'accessible', 'deleted', 'visible', 'purged',
+ 'state', 'data_type', 'file_ext', 'file_size',
+ 'misc_info', 'misc_blurb',
+ 'download_url', 'visualizations', 'display_apps', 'display_types',
+ 'genome_build',
+ // the following are NOT always present DEPENDING ON datatype
+ 'metadata_dbkey',
+ 'metadata_column_names', 'metadata_column_types', 'metadata_columns',
+ 'metadata_comment_lines', 'metadata_data_lines'
+ ];
-// ------------------------------------------------------------------------------------------- logged in user
-spaceghost.then( function(){
-
- // ------------------------------------------------------------------------------------------- INDEX
- this.test.comment( 'index should return a list of summary data for each hda' );
- var histories = this.api.histories.index(),
- lastHistory = histories[0],
- hdaIndex = this.api.hdas.index( lastHistory.id );
- //this.debug( 'hdaIndex:' + this.jsonStr( hdaIndex ) );
+ // ------------------------------------------------------------------------------------------- logged in user
+ spaceghost.then( function(){
+ // ------------------------------------------------------------------------------------------- INDEX
+ this.test.comment( 'index should return a list of summary data for each hda' );
+ var histories = this.api.histories.index(),
+ lastHistory = histories[0],
+ hdaIndex = this.api.hdas.index( lastHistory.id );
+ //this.debug( 'hdaIndex:' + this.jsonStr( hdaIndex ) );
- this.test.assert( utils.isArray( hdaIndex ), "index returned an array: length " + hdaIndex.length );
- this.test.assert( hdaIndex.length >= 1, 'Has at least one hda' );
+ this.test.assert( utils.isArray( hdaIndex ), "index returned an array: length " + hdaIndex.length );
+ this.test.assert( hdaIndex.length >= 1, 'Has at least one hda' );
- var firstHda = hdaIndex[0];
- this.test.assert( this.hasKeys( firstHda, summaryKeys ), 'Has the proper keys' );
+ var firstHda = hdaIndex[0];
+ this.test.assert( this.hasKeys( firstHda, summaryKeys ), 'Has the proper keys' );
- this.test.assert( this.api.isEncodedId( firstHda.id ), 'Id appears well-formed: ' + firstHda.id );
- this.test.assert( firstHda.name === '1.sam', 'Title matches: ' + firstHda.name );
+ this.test.assert( this.api.isEncodedId( firstHda.id ), 'Id appears well-formed: ' + firstHda.id );
+ this.test.assert( firstHda.name === '1.sam', 'Title matches: ' + firstHda.name );
- // ------------------------------------------------------------------------------------------- SHOW
- this.test.comment( 'show should get an HDA details object' );
- var hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- //this.debug( this.jsonStr( hdaShow ) );
- this.test.assert( this.hasKeys( hdaShow, detailKeys ), 'Has the proper keys' );
+ // ------------------------------------------------------------------------------------------- SHOW
+ this.test.comment( 'show should get an HDA details object' );
+ var hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ //this.debug( this.jsonStr( hdaShow ) );
+ this.test.assert( this.hasKeys( hdaShow, detailKeys ), 'Has the proper keys' );
- //TODO: validate data in each hdaShow attribute...
+ //TODO: validate data in each hdaShow attribute...
- // ------------------------------------------------------------------------------------------- INDEX (detailed)
- this.test.comment( 'index should return a list of detailed data for each hda in "ids" when passed' );
- hdaIndex = this.api.hdas.index( lastHistory.id, [ firstHda.id ] );
- this.debug( 'hdaIndex:' + this.jsonStr( hdaIndex ) );
+ // ------------------------------------------------------------------------------------------- INDEX (detailed)
+ this.test.comment( 'index should return a list of detailed data for each hda in "ids" when passed' );
+ hdaIndex = this.api.hdas.index( lastHistory.id, [ firstHda.id ] );
+ this.debug( 'hdaIndex:' + this.jsonStr( hdaIndex ) );
- this.test.assert( utils.isArray( hdaIndex ), "index returned an array: length " + hdaIndex.length );
- this.test.assert( hdaIndex.length >= 1, 'Has at least one hda' );
+ this.test.assert( utils.isArray( hdaIndex ), "index returned an array: length " + hdaIndex.length );
+ this.test.assert( hdaIndex.length >= 1, 'Has at least one hda' );
- firstHda = hdaIndex[0];
- this.test.assert( this.hasKeys( firstHda, detailKeys ), 'Has the proper keys' );
+ firstHda = hdaIndex[0];
+ this.test.assert( this.hasKeys( firstHda, detailKeys ), 'Has the proper keys' );
- //TODO??: validate data in firstHda attribute? we ASSUME it's from a common method as show...
+ //TODO??: validate data in firstHda attribute? we ASSUME it's from a common method as show...
- // ------------------------------------------------------------------------------------------- CREATE
- //TODO: create from_ld_id
- this.test.comment( 'create should allow copying an accessible hda' );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- var returned = this.api.hdas.create( lastHistory.id, {
- source : 'hda',
- content : hdaShow.id
- });
- //this.debug( 'returned:' + this.jsonStr( returned ) );
- this.test.assert( this.hasKeys( returned, detailKeys ), 'Has the proper keys' );
- this.test.assert( typeof returned.id !== 'number' && isNaN( Number( returned.id ) ),
- 'id seems to be encoded: ' + returned.id );
- this.test.assert( typeof returned.history_id !== 'number' && isNaN( Number( returned.history_id ) ),
- 'history_id seems to be encoded: ' + returned.history_id );
-
-
- // ------------------------------------------------------------------------------------------- UPDATE
- // ........................................................................................... idiot proofing
- this.test.comment( 'updating to the current value should return no value (no change)' );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- var returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- name : hdaShow.name
- });
- this.test.assert( this.countKeys( returned ) === 0, "No changed returned: " + this.jsonStr( returned ) );
-
- this.test.comment( 'updating using a nonsense key should NOT fail with an error' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- konamiCode : 'uuddlrlrba'
- });
- this.test.assert( this.countKeys( returned ) === 0, "No changed returned: " + this.jsonStr( returned ) );
-
- this.test.comment( 'updating by attempting to change type should cause an error' );
- this.api.assertRaises( function(){
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- //name : false
- deleted : 'sure why not'
+ // ------------------------------------------------------------------------------------------- CREATE
+ //TODO: create from_ld_id
+ this.test.comment( 'create should allow copying an accessible hda' );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ var returned = this.api.hdas.create( lastHistory.id, {
+ source : 'hda',
+ content : hdaShow.id
});
- }, 400, 'deleted must be a boolean', 'changing deleted type failed' );
+ //this.debug( 'returned:' + this.jsonStr( returned ) );
+ this.test.assert( this.hasKeys( returned, detailKeys ), 'Has the proper keys' );
+ this.test.assert( typeof returned.id !== 'number' && isNaN( Number( returned.id ) ),
+ 'id seems to be encoded: ' + returned.id );
+ this.test.assert( typeof returned.history_id !== 'number' && isNaN( Number( returned.history_id ) ),
+ 'history_id seems to be encoded: ' + returned.history_id );
- // ........................................................................................... name
- this.test.comment( 'update should allow changing the name' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- name : 'New name'
+
+ // ------------------------------------------------------------------------------------------- UPDATE
+ // ........................................................................................... idiot proofing
+ this.test.comment( 'updating to the current value should return no value (no change)' );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ var returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ name : hdaShow.name
+ });
+ this.test.assert( this.countKeys( returned ) === 0, "No changed returned: " + this.jsonStr( returned ) );
+
+ this.test.comment( 'updating using a nonsense key should NOT fail with an error' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ konamiCode : 'uuddlrlrba'
+ });
+ this.test.assert( this.countKeys( returned ) === 0, "No changed returned: " + this.jsonStr( returned ) );
+
+ this.test.comment( 'updating by attempting to change type should cause an error' );
+ this.api.assertRaises( function(){
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ //name : false
+ deleted : 'sure why not'
+ });
+ }, 400, 'deleted must be a boolean', 'changing deleted type failed' );
+
+ // ........................................................................................... name
+ this.test.comment( 'update should allow changing the name' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ name : 'New name'
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.name === 'New name', "Name successfully set via update: " + hdaShow.name );
+
+ this.test.comment( 'update should sanitize any new name' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ name : 'New name'
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.name === 'New name', "Update sanitized name: " + hdaShow.name );
+
+ this.test.comment( 'update should allow unicode in names' );
+ var unicodeName = 'Ржевский сапоги';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ name : unicodeName
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.name === unicodeName, "Update accepted unicode name: " + hdaShow.name );
+
+ this.test.comment( 'update should allow escaped quotations in names' );
+ var quotedName = '"Bler"';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ name : quotedName
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.name === quotedName,
+ "Update accepted escaped quotations in name: " + hdaShow.name );
+
+
+ // ........................................................................................... deleted
+ this.test.comment( 'update should allow changing the deleted flag' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ deleted: true
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id, true );
+ this.test.assert( hdaShow.deleted === true, "Update set the deleted flag: " + hdaShow.deleted );
+
+ this.test.comment( 'update should allow changing the deleted flag back' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ deleted: false
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.deleted === false, "Update set the deleted flag: " + hdaShow.deleted );
+
+
+ // ........................................................................................... visible/hidden
+ this.test.comment( 'update should allow changing the visible flag' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ visible: false
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.visible === false, "Update set the visible flag: " + hdaShow.visible );
+
+
+ // ........................................................................................... genome/dbkey
+ this.test.comment( 'update should allow changing the genome_build' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ genome_build : 'hg18'
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ //this.debug( 'hdaShow:\n' + this.jsonStr( hdaShow ) );
+ this.test.assert( hdaShow.genome_build === 'hg18',
+ "genome_build successfully set via update: " + hdaShow.genome_build );
+ this.test.assert( hdaShow.metadata_dbkey === 'hg18',
+ "metadata_dbkey successfully set via the same update: " + hdaShow.metadata_dbkey );
+ this.test.comment( 'update should sanitize any genome_build' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ genome_build : 'hg18'
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.genome_build === 'hg18',
+ "Update sanitized genome_build: " + hdaShow.genome_build );
+ this.test.assert( hdaShow.metadata_dbkey === 'hg18',
+ "metadata_dbkey successfully set via the same update: " + hdaShow.metadata_dbkey );
+
+ this.test.comment( 'update should allow unicode in genome builds' );
+ var unicodeBuild = 'Ржевский18';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ genome_build : unicodeBuild
+ });
+ this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.debug( 'hdaShow:\n' + this.jsonStr( hdaShow ) );
+ this.test.assert( hdaShow.genome_build === unicodeBuild,
+ "Update accepted unicode genome_build: " + hdaShow.genome_build );
+
+ // ........................................................................................... misc_info/info
+ this.test.comment( 'update should allow changing the misc_info' );
+ var newInfo = 'I\'ve made a huge mistake.';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ misc_info : newInfo
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.misc_info === newInfo,
+ "misc_info successfully set via update: " + hdaShow.misc_info );
+
+ this.test.comment( 'update should sanitize any misc_info' );
+ var newInfo = 'You\'re going to get hop-ons.';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ misc_info : newInfo + ''
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.misc_info === newInfo,
+ "Update sanitized misc_info: " + hdaShow.misc_info );
+
+ this.test.comment( 'update should allow unicode in misc_info' );
+ var unicodeInfo = '여보!';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ misc_info : unicodeInfo
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.misc_info === unicodeInfo,
+ "Update accepted unicode misc_info: " + hdaShow.misc_info );
+
+ // ........................................................................................... annotation
+ // currently fails because no annotation is returned in details
+ this.test.comment( 'update should allow changing the annotation' );
+ var newAnnotation = 'Found this sample on a movie theatre floor';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ annotation : newAnnotation
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.annotation === newAnnotation,
+ "Annotation successfully set via update: " + hdaShow.annotation );
+
+ this.test.comment( 'update should sanitize any new annotation' );
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ annotation : 'New annotation'
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.annotation === 'New annotation',
+ "Update sanitized annotation: " + hdaShow.annotation );
+
+ this.test.comment( 'update should allow unicode in annotations' );
+ var unicodeAnnotation = 'お願いは、それが落下させない';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ annotation : unicodeAnnotation
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.annotation === unicodeAnnotation,
+ "Update accepted unicode annotation: " + hdaShow.annotation );
+
+ this.test.comment( 'update should allow escaped quotations in annotations' );
+ var quotedAnnotation = '"Bler"';
+ returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
+ annotation : quotedAnnotation
+ });
+ //this.debug( 'returned:\n' + this.jsonStr( returned ) );
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.annotation === quotedAnnotation,
+ "Update accepted escaped quotations in annotation: " + hdaShow.annotation );
+
+ // ------------------------------------------------------------------------------------------- ERRORS
+ this.test.comment( 'create should error with "Please define the source" when the param "from_ld_id" is not used' );
+ this.api.assertRaises( function(){
+ this.api.hdas.create( lastHistory.id, { bler: 'bler' } );
+ }, 400, "must be either 'library' or 'hda'", 'create with no source failed' );
+
+ this.test.comment( 'updating using a nonsense key should fail silently' );
+ returned = this.api.hdas.update( lastHistory.id, hdaShow.id, {
+ konamiCode : 'uuddlrlrba'
+ });
+ this.test.assert( returned.konamiCode === undefined, 'key was not set: ' + returned.konamiCode );
+
+ spaceghost.test.comment( 'A bad id should throw an error when using show' );
+ this.api.assertRaises( function(){
+ this.api.hdas.show( lastHistory.id, '1234123412341234' );
+ }, 400, 'unable to decode', 'Bad Request with invalid id: show' );
+ spaceghost.test.comment( 'A bad id should throw an error when using update' );
+ this.api.assertRaises( function(){
+ this.api.hdas.update( lastHistory.id, '1234123412341234', {} );
+ }, 400, 'unable to decode', 'Bad Request with invalid id: update' );
+ spaceghost.test.comment( 'A bad id should throw an error when using delete' );
+ this.api.assertRaises( function(){
+ this.api.hdas.delete_( lastHistory.id, '1234123412341234' );
+ }, 400, 'unable to decode', 'Bad Request with invalid id: delete' );
+ spaceghost.test.comment( 'A bad id should throw an error when using undelete' );
+
+ this.test.comment( 'updating by attempting to change type should cause an error' );
+ [ 'name', 'annotation', 'genome_build', 'misc_info' ].forEach( function( key ){
+ var updatedAttrs = {};
+ updatedAttrs[ key ] = false;
+ spaceghost.api.assertRaises( function(){
+ returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, updatedAttrs );
+ }, 400, key + ' must be a string or unicode', 'type validation error' );
+ });
+ [ 'deleted', 'visible' ].forEach( function( key ){
+ var updatedAttrs = {};
+ updatedAttrs[ key ] = 'straaang';
+ spaceghost.api.assertRaises( function(){
+ returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, updatedAttrs );
+ }, 400, key + ' must be a boolean', 'type validation error' );
+ });
+ [ 'you\'re it', [ true ] ].forEach( function( badVal ){
+ spaceghost.api.assertRaises( function(){
+ returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, { tags: badVal });
+ }, 400, 'tags must be a list', 'type validation error' );
+ });
+
+ // ------------------------------------------------------------------------------------------- DELETE
+ this.test.comment( 'calling delete on an hda should mark it as deleted but not change the history size' );
+ lastHistory = this.api.histories.show( lastHistory.id );
+ var sizeBeforeDelete = lastHistory.nice_size;
+
+ returned = this.api.hdas.delete_( lastHistory.id, firstHda.id );
+ //this.debug( this.jsonStr( returned ) );
+
+ hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
+ this.test.assert( hdaShow.deleted, 'hda is marked deleted' );
+ lastHistory = this.api.histories.show( lastHistory.id );
+ this.test.assert( lastHistory.nice_size === sizeBeforeDelete, 'history size has not changed' );
+
+ // by default, purging fails bc uni.ini:allow_user_dataset_purge=False
+ this.api.assertRaises( function(){
+ returned = this.api.hdas.delete_( lastHistory.id, firstHda.id, { purge : true });
+ }, 403, 'This instance does not allow user dataset purging', 'Purge failed' );
+ /*
+ */
});
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.name === 'New name', "Name successfully set via update: " + hdaShow.name );
-
- this.test.comment( 'update should sanitize any new name' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- name : 'New name'
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.name === 'New name', "Update sanitized name: " + hdaShow.name );
-
- this.test.comment( 'update should allow unicode in names' );
- var unicodeName = 'Ржевский сапоги';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- name : unicodeName
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.name === unicodeName, "Update accepted unicode name: " + hdaShow.name );
-
- this.test.comment( 'update should allow escaped quotations in names' );
- var quotedName = '"Bler"';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- name : quotedName
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.name === quotedName,
- "Update accepted escaped quotations in name: " + hdaShow.name );
+ //spaceghost.user.logout();
- // ........................................................................................... deleted
- this.test.comment( 'update should allow changing the deleted flag' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- deleted: true
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id, true );
- this.test.assert( hdaShow.deleted === true, "Update set the deleted flag: " + hdaShow.deleted );
-
- this.test.comment( 'update should allow changing the deleted flag back' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- deleted: false
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.deleted === false, "Update set the deleted flag: " + hdaShow.deleted );
-
-
- // ........................................................................................... visible/hidden
- this.test.comment( 'update should allow changing the visible flag' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- visible: false
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.visible === false, "Update set the visible flag: " + hdaShow.visible );
-
-
- // ........................................................................................... genome_build/dbkey
- this.test.comment( 'update should allow changing the genome_build' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- genome_build : 'hg18'
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- //this.debug( 'hdaShow:\n' + this.jsonStr( hdaShow ) );
- this.test.assert( hdaShow.genome_build === 'hg18',
- "genome_build successfully set via update: " + hdaShow.genome_build );
- this.test.assert( hdaShow.metadata_dbkey === 'hg18',
- "metadata_dbkey successfully set via the same update: " + hdaShow.metadata_dbkey );
- this.test.comment( 'update should sanitize any genome_build' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- genome_build : 'hg18'
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.genome_build === 'hg18',
- "Update sanitized genome_build: " + hdaShow.genome_build );
- this.test.assert( hdaShow.metadata_dbkey === 'hg18',
- "metadata_dbkey successfully set via the same update: " + hdaShow.metadata_dbkey );
-
- this.test.comment( 'update should allow unicode in genome builds' );
- var unicodeBuild = 'Ржевский18';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- genome_build : unicodeBuild
- });
- this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.debug( 'hdaShow:\n' + this.jsonStr( hdaShow ) );
- this.test.assert( hdaShow.genome_build === unicodeBuild,
- "Update accepted unicode genome_build: " + hdaShow.genome_build );
-
- // ........................................................................................... misc_info/info
- this.test.comment( 'update should allow changing the misc_info' );
- var newInfo = 'I\'ve made a huge mistake.';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- misc_info : newInfo
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.misc_info === newInfo,
- "misc_info successfully set via update: " + hdaShow.misc_info );
-
- this.test.comment( 'update should sanitize any misc_info' );
- var newInfo = 'You\'re going to get hop-ons.';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- misc_info : newInfo + ''
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.misc_info === newInfo,
- "Update sanitized misc_info: " + hdaShow.misc_info );
-
- this.test.comment( 'update should allow unicode in misc_info' );
- var unicodeInfo = '여보!';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- misc_info : unicodeInfo
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.misc_info === unicodeInfo,
- "Update accepted unicode misc_info: " + hdaShow.misc_info );
-
- // ........................................................................................... annotation
- // currently fails because no annotation is returned in details
- this.test.comment( 'update should allow changing the annotation' );
- var newAnnotation = 'Found this sample on a movie theatre floor';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- annotation : newAnnotation
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.annotation === newAnnotation,
- "Annotation successfully set via update: " + hdaShow.annotation );
-
- this.test.comment( 'update should sanitize any new annotation' );
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- annotation : 'New annotation'
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.annotation === 'New annotation',
- "Update sanitized annotation: " + hdaShow.annotation );
-
- this.test.comment( 'update should allow unicode in annotations' );
- var unicodeAnnotation = 'お願いは、それが落下させない';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- annotation : unicodeAnnotation
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.annotation === unicodeAnnotation,
- "Update accepted unicode annotation: " + hdaShow.annotation );
-
- this.test.comment( 'update should allow escaped quotations in annotations' );
- var quotedAnnotation = '"Bler"';
- returned = this.api.hdas.update( lastHistory.id, firstHda.id, {
- annotation : quotedAnnotation
- });
- //this.debug( 'returned:\n' + this.jsonStr( returned ) );
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.annotation === quotedAnnotation,
- "Update accepted escaped quotations in annotation: " + hdaShow.annotation );
-
- // ------------------------------------------------------------------------------------------- ERRORS
- this.test.comment( 'create should error with "Please define the source" when the param "from_ld_id" is not used' );
- this.api.assertRaises( function(){
- this.api.hdas.create( lastHistory.id, { bler: 'bler' } );
- }, 400, "must be either 'library' or 'hda'", 'create with no source failed' );
-
- this.test.comment( 'updating using a nonsense key should fail silently' );
- returned = this.api.hdas.update( lastHistory.id, hdaShow.id, {
- konamiCode : 'uuddlrlrba'
- });
- this.test.assert( returned.konamiCode === undefined, 'key was not set: ' + returned.konamiCode );
-
- spaceghost.test.comment( 'A bad id should throw an error when using show' );
- this.api.assertRaises( function(){
- this.api.hdas.show( lastHistory.id, '1234123412341234' );
- }, 400, 'unable to decode', 'Bad Request with invalid id: show' );
- spaceghost.test.comment( 'A bad id should throw an error when using update' );
- this.api.assertRaises( function(){
- this.api.hdas.update( lastHistory.id, '1234123412341234', {} );
- }, 400, 'unable to decode', 'Bad Request with invalid id: update' );
- spaceghost.test.comment( 'A bad id should throw an error when using delete' );
- this.api.assertRaises( function(){
- this.api.hdas.delete_( lastHistory.id, '1234123412341234' );
- }, 400, 'unable to decode', 'Bad Request with invalid id: delete' );
- spaceghost.test.comment( 'A bad id should throw an error when using undelete' );
-
- this.test.comment( 'updating by attempting to change type should cause an error' );
- [ 'name', 'annotation', 'genome_build', 'misc_info' ].forEach( function( key ){
- var updatedAttrs = {};
- updatedAttrs[ key ] = false;
- spaceghost.api.assertRaises( function(){
- returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, updatedAttrs );
- }, 400, key + ' must be a string or unicode', 'type validation error' );
- });
- [ 'deleted', 'visible' ].forEach( function( key ){
- var updatedAttrs = {};
- updatedAttrs[ key ] = 'straaang';
- spaceghost.api.assertRaises( function(){
- returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, updatedAttrs );
- }, 400, key + ' must be a boolean', 'type validation error' );
- });
- [ 'you\'re it', [ true ] ].forEach( function( badVal ){
- spaceghost.api.assertRaises( function(){
- returned = spaceghost.api.hdas.update( hdaShow.history_id, hdaShow.id, { tags: badVal });
- }, 400, 'tags must be a list', 'type validation error' );
- });
-
- // ------------------------------------------------------------------------------------------- DELETE
- this.test.comment( 'calling delete on an hda should mark it as deleted but not change the history size' );
- lastHistory = this.api.histories.show( lastHistory.id );
- var sizeBeforeDelete = lastHistory.nice_size;
-
- returned = this.api.hdas.delete_( lastHistory.id, firstHda.id );
- //this.debug( this.jsonStr( returned ) );
-
- hdaShow = this.api.hdas.show( lastHistory.id, firstHda.id );
- this.test.assert( hdaShow.deleted, 'hda is marked deleted' );
- lastHistory = this.api.histories.show( lastHistory.id );
- this.test.assert( lastHistory.nice_size === sizeBeforeDelete, 'history size has not changed' );
-
- // by default, purging fails bc uni.ini:allow_user_dataset_purge=False
- this.api.assertRaises( function(){
- returned = this.api.hdas.delete_( lastHistory.id, firstHda.id, { purge : true });
- }, 403, 'This instance does not allow user dataset purging', 'Purge failed' );
-/*
-*/
-});
-//spaceghost.user.logout();
-
-
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-history-permission-tests.js b/test/casperjs/api-history-permission-tests.js
index c85f934839e..a9eebf07b96 100644
--- a/test/casperjs/api-history-permission-tests.js
+++ b/test/casperjs/api-history-permission-tests.js
@@ -8,254 +8,255 @@ spaceghost.test.begin( 'Test permissions for accessible, published, and inaccess
+ 'over the API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-var email = spaceghost.user.getRandomEmail(),
- password = '123456';
-if( spaceghost.fixtureData.testUser ){
- email = spaceghost.fixtureData.testUser.email;
- password = spaceghost.fixtureData.testUser.password;
-}
-var email2 = spaceghost.user.getRandomEmail(),
- password2 = '123456';
-if( spaceghost.fixtureData.testUser2 ){
- email2 = spaceghost.fixtureData.testUser2.email;
- password2 = spaceghost.fixtureData.testUser2.password;
-}
+ // =================================================================== SET UP
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+ }
+ var email2 = spaceghost.user.getRandomEmail(),
+ password2 = '123456';
+ if( spaceghost.fixtureData.testUser2 ){
+ email2 = spaceghost.fixtureData.testUser2.email;
+ password2 = spaceghost.fixtureData.testUser2.password;
+ }
-var inaccessibleHistory, accessibleHistory, publishedHistory,
- inaccessibleHdas, accessibleHdas, publishedHdas,
- accessibleLink;
+ var inaccessibleHistory, accessibleHistory, publishedHistory,
+ inaccessibleHdas, accessibleHdas, publishedHdas,
+ accessibleLink;
-// =================================================================== TESTS
-//// ------------------------------------------------------------------------------------------- create 3 histories
-spaceghost.user.loginOrRegisterUser( email, password );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
- // create three histories: make the 2nd importable (via the API), and the third published
+ // =================================================================== TESTS
+ //// ------------------------------------------------------------------------------------------- create 3 histories
+ spaceghost.user.loginOrRegisterUser( email, password );
+ spaceghost.openHomePage().then( function(){
+ this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
+ // create three histories: make the 2nd importable (via the API), and the third published
- this.test.comment( 'importable, slug, and published should all be returned by show and initially off' );
- // make the current the inaccessible one
- inaccessibleHistory = this.api.histories.show( this.api.histories.index()[0].id );
- this.test.assert( this.hasKeys( inaccessibleHistory, [ 'id', 'name', 'slug', 'importable', 'published' ] ),
- 'Has the proper keys' );
- this.test.assert( inaccessibleHistory.slug === null,
- 'initial slug is null: ' + inaccessibleHistory.slug );
- this.test.assert( inaccessibleHistory.importable === false,
- 'initial importable is false: ' + inaccessibleHistory.importable );
- this.test.assert( inaccessibleHistory.published === false,
- 'initial published is false: ' + inaccessibleHistory.published );
- this.api.histories.update( inaccessibleHistory.id, { name: 'inaccessible' });
- inaccessibleHistory = this.api.histories.show( inaccessibleHistory.id );
+ this.test.comment( 'importable, slug, and published should all be returned by show and initially off' );
+ // make the current the inaccessible one
+ inaccessibleHistory = this.api.histories.show( this.api.histories.index()[0].id );
+ this.test.assert( this.hasKeys( inaccessibleHistory, [ 'id', 'name', 'slug', 'importable', 'published' ] ),
+ 'Has the proper keys' );
+ this.test.assert( inaccessibleHistory.slug === null,
+ 'initial slug is null: ' + inaccessibleHistory.slug );
+ this.test.assert( inaccessibleHistory.importable === false,
+ 'initial importable is false: ' + inaccessibleHistory.importable );
+ this.test.assert( inaccessibleHistory.published === false,
+ 'initial published is false: ' + inaccessibleHistory.published );
+ this.api.histories.update( inaccessibleHistory.id, { name: 'inaccessible' });
+ inaccessibleHistory = this.api.histories.show( inaccessibleHistory.id );
+
+ this.test.comment( 'Setting importable to true should create a slug, ' +
+ 'username_and_slug, and importable === true' );
+ accessibleHistory = this.api.histories.create({ name: 'accessible' });
+ var returned = this.api.histories.update( accessibleHistory.id, {
+ importable : true
+ });
+ this.debug( this.jsonStr( returned ) );
+ accessibleHistory = this.api.histories.show( accessibleHistory.id );
+ this.test.assert( this.hasKeys( accessibleHistory, [ 'username_and_slug' ] ),
+ 'Has the proper keys' );
+ this.test.assert( accessibleHistory.slug === 'accessible',
+ 'slug is not null: ' + accessibleHistory.slug );
+ this.test.assert( accessibleHistory.importable,
+ 'importable is true: ' + accessibleHistory.importable );
+ accessibleLink = 'u/' + email.replace( '@test.test', '' ) + '/h/accessible';
+ this.test.assert( accessibleHistory.username_and_slug === accessibleLink,
+ 'username_and_slug is proper: ' + accessibleHistory.username_and_slug );
+
+ this.test.comment( 'Setting published to true should make accessible and published === true' );
+ publishedHistory = this.api.histories.create({ name: 'published' });
+ returned = this.api.histories.update( publishedHistory.id, {
+ published : true
+ });
+ this.debug( this.jsonStr( returned ) );
+ publishedHistory = this.api.histories.show( publishedHistory.id );
+ this.test.assert( this.hasKeys( publishedHistory, [ 'username_and_slug' ] ),
+ 'Has the proper keys' );
+ this.test.assert( publishedHistory.published,
+ 'published is true: ' + publishedHistory.published );
+ this.test.assert( publishedHistory.importable,
+ 'importable is true: ' + publishedHistory.importable );
+ this.test.assert( publishedHistory.slug === 'published',
+ 'slug is not null: ' + publishedHistory.slug );
+ accessibleLink = 'u/' + email.replace( '@test.test', '' ) + '/h/published';
+ this.test.assert( publishedHistory.username_and_slug === accessibleLink,
+ 'username_and_slug is proper: ' + publishedHistory.username_and_slug );
- this.test.comment( 'Setting importable to true should create a slug, username_and_slug, and importable === true' );
- accessibleHistory = this.api.histories.create({ name: 'accessible' });
- var returned = this.api.histories.update( accessibleHistory.id, {
- importable : true
});
- this.debug( this.jsonStr( returned ) );
- accessibleHistory = this.api.histories.show( accessibleHistory.id );
- this.test.assert( this.hasKeys( accessibleHistory, [ 'username_and_slug' ] ),
- 'Has the proper keys' );
- this.test.assert( accessibleHistory.slug === 'accessible',
- 'slug is not null: ' + accessibleHistory.slug );
- this.test.assert( accessibleHistory.importable,
- 'importable is true: ' + accessibleHistory.importable );
- accessibleLink = 'u/' + email.replace( '@test.test', '' ) + '/h/accessible';
- this.test.assert( accessibleHistory.username_and_slug === accessibleLink,
- 'username_and_slug is proper: ' + accessibleHistory.username_and_slug );
- this.test.comment( 'Setting published to true should make accessible and published === true' );
- publishedHistory = this.api.histories.create({ name: 'published' });
- returned = this.api.histories.update( publishedHistory.id, {
- published : true
+ //// ------------------------------------------------------------------------------------------- upload some files
+ spaceghost.then( function(){
+ this.api.tools.thenUpload( inaccessibleHistory.id, { filepath: '../../test-data/1.bed' });
+ this.api.tools.thenUpload( accessibleHistory.id, { filepath: '../../test-data/1.bed' });
+ this.api.tools.thenUpload( publishedHistory.id, { filepath: '../../test-data/1.bed' });
});
- this.debug( this.jsonStr( returned ) );
- publishedHistory = this.api.histories.show( publishedHistory.id );
- this.test.assert( this.hasKeys( publishedHistory, [ 'username_and_slug' ] ),
- 'Has the proper keys' );
- this.test.assert( publishedHistory.published,
- 'published is true: ' + publishedHistory.published );
- this.test.assert( publishedHistory.importable,
- 'importable is true: ' + publishedHistory.importable );
- this.test.assert( publishedHistory.slug === 'published',
- 'slug is not null: ' + publishedHistory.slug );
- accessibleLink = 'u/' + email.replace( '@test.test', '' ) + '/h/published';
- this.test.assert( publishedHistory.username_and_slug === accessibleLink,
- 'username_and_slug is proper: ' + publishedHistory.username_and_slug );
-
-});
-
-//// ------------------------------------------------------------------------------------------- upload some files
-spaceghost.then( function(){
- this.api.tools.thenUpload( inaccessibleHistory.id, { filepath: '../../test-data/1.bed' });
- this.api.tools.thenUpload( accessibleHistory.id, { filepath: '../../test-data/1.bed' });
- this.api.tools.thenUpload( publishedHistory.id, { filepath: '../../test-data/1.bed' });
-});
-spaceghost.then( function(){
- // check that they're there
- inaccessibleHdas = this.api.hdas.index( inaccessibleHistory.id ),
- accessibleHdas = this.api.hdas.index( accessibleHistory.id ),
- publishedHdas = this.api.hdas.index( publishedHistory.id );
- this.test.comment( '---- adding datasets' );
- this.test.assert( inaccessibleHdas.length === 1,
- 'uploaded file to inaccessible: ' + inaccessibleHdas.length );
- this.test.assert( accessibleHdas.length === 1,
- 'uploaded file to accessible: ' + accessibleHdas.length );
- this.test.assert( publishedHdas.length === 1,
- 'uploaded file to published: ' + publishedHdas.length );
-});
-spaceghost.user.logout();
-
-//// ------------------------------------------------------------------------------------------- log in user2
-function testReadFunctionsOnAccessible( history, hdas ){
- this.test.comment( '---- testing read/accessibility functions for ACCESSIBLE history: ' + history.name );
-
- // read functions for history
- this.test.comment( 'show should work for history: ' + history.name );
- this.test.assert( this.api.histories.show( history.id ).id === history.id,
- 'show worked' );
- this.test.comment( 'copying should work for history: ' + history.name );
- var returned = this.api.histories.create({ history_id : history.id });
- this.test.assert( returned.name === "Copy of '" + history.name + "'",
- 'copied name matches: ' + returned.name );
-
- // read functions for history contents
- this.test.comment( 'index of history contents should work for history: ' + history.name );
- this.test.assert( this.api.hdas.index( history.id ).length === 1,
- 'hda index worked' );
- this.test.comment( 'showing of history contents should work for history: ' + history.name );
- this.test.assert( this.api.hdas.show( history.id, hdas[0].id ).id === hdas[0].id,
- 'hda show worked' );
-
- this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
- + ' should work from accessible history: ' + history.name );
- returned = this.api.hdas.create( this.api.histories.index()[0].id, {
- source : 'hda',
- content : hdas[0].id
+ spaceghost.then( function(){
+ // check that they're there
+ inaccessibleHdas = this.api.hdas.index( inaccessibleHistory.id ),
+ accessibleHdas = this.api.hdas.index( accessibleHistory.id ),
+ publishedHdas = this.api.hdas.index( publishedHistory.id );
+ this.test.comment( '---- adding datasets' );
+ this.test.assert( inaccessibleHdas.length === 1,
+ 'uploaded file to inaccessible: ' + inaccessibleHdas.length );
+ this.test.assert( accessibleHdas.length === 1,
+ 'uploaded file to accessible: ' + accessibleHdas.length );
+ this.test.assert( publishedHdas.length === 1,
+ 'uploaded file to published: ' + publishedHdas.length );
});
- this.test.assert( returned.name === hdas[0].name, 'successful hda copy from: ' + returned.name );
-}
+ spaceghost.user.logout();
-function testReadFunctionsOnInaccessible( history, hdas ){
- this.test.comment( '---- testing read/accessibility functions for INACCESSIBLE history: ' + history.name );
+ //// ------------------------------------------------------------------------------------------- log in user2
+ function testReadFunctionsOnAccessible( history, hdas ){
+ this.test.comment( '---- testing read/accessibility functions for ACCESSIBLE history: ' + history.name );
- // read functions for history
- this.test.comment( 'show should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.show( history.id );
- }, 403, 'History is not accessible to the current user', 'show failed with error' );
- this.test.comment( 'copying should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.create({ history_id : history.id });
- }, 403, 'History is not accessible to the current user', 'copy failed with error' );
+ // read functions for history
+ this.test.comment( 'show should work for history: ' + history.name );
+ this.test.assert( this.api.histories.show( history.id ).id === history.id,
+ 'show worked' );
+ this.test.comment( 'copying should work for history: ' + history.name );
+ var returned = this.api.histories.create({ history_id : history.id });
+ this.test.assert( returned.name === "Copy of '" + history.name + "'",
+ 'copied name matches: ' + returned.name );
- // read functions for history contents
- this.test.comment( 'index and show of history contents should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.index( history.id );
- }, 403, 'History is not accessible to the current user', 'hda index failed with error' );
- this.api.assertRaises( function(){
- this.api.hdas.show( history.id, hdas[0].id );
- }, 403, 'History is not accessible to the current user', 'hda show failed with error' );
+ // read functions for history contents
+ this.test.comment( 'index of history contents should work for history: ' + history.name );
+ this.test.assert( this.api.hdas.index( history.id ).length === 1,
+ 'hda index worked' );
+ this.test.comment( 'showing of history contents should work for history: ' + history.name );
+ this.test.assert( this.api.hdas.show( history.id, hdas[0].id ).id === hdas[0].id,
+ 'hda show worked' );
- this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
- + ' from an inaccessible history should fail for: ' + history.name );
- this.api.assertRaises( function(){
- var returned = this.api.hdas.create( this.api.histories.index()[0].id, {
+ this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
+ + ' should work from accessible history: ' + history.name );
+ returned = this.api.hdas.create( this.api.histories.index()[0].id, {
source : 'hda',
content : hdas[0].id
});
- this.debug( this.jsonStr( returned ) );
- }, 403, 'History is not accessible to the current user', 'hda copy from failed with error' );
+ this.test.assert( returned.name === hdas[0].name, 'successful hda copy from: ' + returned.name );
+ }
-}
+ function testReadFunctionsOnInaccessible( history, hdas ){
+ this.test.comment( '---- testing read/accessibility functions for INACCESSIBLE history: ' + history.name );
-function testWriteFunctions( history, hdas ){
- this.test.comment( '---- testing write/ownership functions for history: ' + history.name );
+ // read functions for history
+ this.test.comment( 'show should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.show( history.id );
+ }, 403, 'History is not accessible to the current user', 'show failed with error' );
+ this.test.comment( 'copying should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.create({ history_id : history.id });
+ }, 403, 'History is not accessible to the current user', 'copy failed with error' );
- this.test.comment( 'update should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.update( history.id, { deleted: true });
- }, 403, 'History is not owned by the current user', 'update failed with error' );
- this.test.comment( 'delete should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.histories.delete_( history.id );
- }, 403, 'History is not owned by the current user', 'delete failed with error' );
+ // read functions for history contents
+ this.test.comment( 'index and show of history contents should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.index( history.id );
+ }, 403, 'History is not accessible to the current user', 'hda index failed with error' );
+ this.api.assertRaises( function(){
+ this.api.hdas.show( history.id, hdas[0].id );
+ }, 403, 'History is not accessible to the current user', 'hda show failed with error' );
- this.test.comment( 'hda updating should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.update( history.id, hdas[0].id, { deleted: true });
- }, 403, 'HistoryDatasetAssociation is not owned by the current user', 'hda update failed with error' );
- this.test.comment( 'hda deletion should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.delete_( history.id, hdas[0].id );
- }, 403, 'HistoryDatasetAssociation is not owned by the current user', 'hda delete failed with error' );
+ this.test.comment( 'Attempting to copy an accessible hda (default is accessible)'
+ + ' from an inaccessible history should fail for: ' + history.name );
+ this.api.assertRaises( function(){
+ var returned = this.api.hdas.create( this.api.histories.index()[0].id, {
+ source : 'hda',
+ content : hdas[0].id
+ });
+ this.debug( this.jsonStr( returned ) );
+ }, 403, 'History is not accessible to the current user', 'hda copy from failed with error' );
- this.test.comment( 'copying hda into history should fail for history: ' + history.name );
- this.api.assertRaises( function(){
- this.api.hdas.create( history.id, {
- source : 'hda',
- // should error before it checks the id
- content : 'bler'
+ }
+
+ function testWriteFunctions( history, hdas ){
+ this.test.comment( '---- testing write/ownership functions for history: ' + history.name );
+
+ this.test.comment( 'update should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.update( history.id, { deleted: true });
+ }, 403, 'History is not owned by the current user', 'update failed with error' );
+ this.test.comment( 'delete should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.histories.delete_( history.id );
+ }, 403, 'History is not owned by the current user', 'delete failed with error' );
+
+ this.test.comment( 'hda updating should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.update( history.id, hdas[0].id, { deleted: true });
+ }, 403, 'HistoryDatasetAssociation is not owned by the current user', 'hda update failed with error' );
+ this.test.comment( 'hda deletion should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.delete_( history.id, hdas[0].id );
+ }, 403, 'HistoryDatasetAssociation is not owned by the current user', 'hda delete failed with error' );
+
+ this.test.comment( 'copying hda into history should fail for history: ' + history.name );
+ this.api.assertRaises( function(){
+ this.api.hdas.create( history.id, {
+ source : 'hda',
+ // should error before it checks the id
+ content : 'bler'
+ });
+ }, 403, 'History is not owned by the current user', 'hda copy to failed' );
+ }
+
+ function testInaccessible( history, hdas ){
+ testReadFunctionsOnInaccessible.call( this, history, hdas );
+ testWriteFunctions.call( this, history, hdas );
+ }
+
+ function testAccessible( history, hdas ){
+ testReadFunctionsOnAccessible.call( this, history, hdas );
+ testWriteFunctions.call( this, history, hdas );
+ }
+
+ spaceghost.user.loginOrRegisterUser( email2, password2 );
+ spaceghost.openHomePage().then( function(){
+ this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
+ testInaccessible.call( spaceghost, inaccessibleHistory, inaccessibleHdas );
+ testAccessible.call( spaceghost, accessibleHistory, accessibleHdas );
+ testAccessible.call( spaceghost, publishedHistory, publishedHdas );
+ });
+ spaceghost.user.logout();
+
+
+ //// ------------------------------------------------------------------------------------------- user1 revoke perms
+ spaceghost.user.loginOrRegisterUser( email, password );
+ spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+ this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
+ this.test.comment( 'revoking perms should prevent access' );
+ this.api.histories.update( accessibleHistory.id, {
+ importable : false
});
- }, 403, 'History is not owned by the current user', 'hda copy to failed' );
-}
+ var returned = this.api.histories.show( accessibleHistory.id );
+ this.test.assert( !returned.importable, 'now not importable' );
+ this.test.assert( !returned.published, '(still not published)' );
+ this.test.assert( !!returned.slug, '(slug still set) ' + returned.slug );
-function testInaccessible( history, hdas ){
- testReadFunctionsOnInaccessible.call( this, history, hdas );
- testWriteFunctions.call( this, history, hdas );
-}
-
-function testAccessible( history, hdas ){
- testReadFunctionsOnAccessible.call( this, history, hdas );
- testWriteFunctions.call( this, history, hdas );
-}
-
-spaceghost.user.loginOrRegisterUser( email2, password2 );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
- testInaccessible.call( spaceghost, inaccessibleHistory, inaccessibleHdas );
- testAccessible.call( spaceghost, accessibleHistory, accessibleHdas );
- testAccessible.call( spaceghost, publishedHistory, publishedHdas );
-});
-spaceghost.user.logout();
-
-
-//// ------------------------------------------------------------------------------------------- user1 revoke perms
-spaceghost.user.loginOrRegisterUser( email, password );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
- this.test.comment( 'revoking perms should prevent access' );
- this.api.histories.update( accessibleHistory.id, {
- importable : false
+ this.api.histories.update( publishedHistory.id, {
+ importable : false,
+ published : false
+ });
+ returned = this.api.histories.show( publishedHistory.id );
+ this.test.assert( !returned.importable, 'now not importable' );
+ this.test.assert( !returned.published, 'now not published' );
+ this.test.assert( !!returned.slug, '(slug still set) ' + returned.slug );
});
- var returned = this.api.histories.show( accessibleHistory.id );
- this.test.assert( !returned.importable, 'now not importable' );
- this.test.assert( !returned.published, '(still not published)' );
- this.test.assert( !!returned.slug, '(slug still set) ' + returned.slug );
+ spaceghost.user.logout();
- this.api.histories.update( publishedHistory.id, {
- importable : false,
- published : false
+
+ //// ------------------------------------------------------------------------------------------- user2 retry perms
+ spaceghost.user.loginOrRegisterUser( email2, password2 );
+ spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+ this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
+ testInaccessible.call( spaceghost, accessibleHistory, accessibleHdas );
+ testInaccessible.call( spaceghost, publishedHistory, publishedHdas );
});
- returned = this.api.histories.show( publishedHistory.id );
- this.test.assert( !returned.importable, 'now not importable' );
- this.test.assert( !returned.published, 'now not published' );
- this.test.assert( !!returned.slug, '(slug still set) ' + returned.slug );
-});
-spaceghost.user.logout();
+ spaceghost.user.logout();
-//// ------------------------------------------------------------------------------------------- user2 retry perms
-spaceghost.user.loginOrRegisterUser( email2, password2 );
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- this.test.comment( '(logged in as ' + this.user.loggedInAs() + ')' );
- testInaccessible.call( spaceghost, accessibleHistory, accessibleHdas );
- testInaccessible.call( spaceghost, publishedHistory, publishedHdas );
-});
-spaceghost.user.logout();
-
-
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-history-tests.js b/test/casperjs/api-history-tests.js
index 719232a7dad..cd55e27ab5b 100644
--- a/test/casperjs/api-history-tests.js
+++ b/test/casperjs/api-history-tests.js
@@ -18,7 +18,7 @@ spaceghost.user.loginOrRegisterUser( email, password );
// =================================================================== TESTS
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+spaceghost.openHomePage().then( function(){
// ------------------------------------------------------------------------------------------- INDEX
this.test.comment( 'index should get a list of histories' );
diff --git a/test/casperjs/api-tool-tests.js b/test/casperjs/api-tool-tests.js
index 25a6867c1e8..a0e886b565c 100644
--- a/test/casperjs/api-tool-tests.js
+++ b/test/casperjs/api-tool-tests.js
@@ -92,7 +92,7 @@ function attemptShowOnAllTools(){
this.debug( this.jsonStr( set ) );
}
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
+spaceghost.openHomePage().then( function(){
// ------------------------------------------------------------------------------------------- INDEX
// ........................................................................................... (defaults)
diff --git a/test/casperjs/api-user-tests.js b/test/casperjs/api-user-tests.js
index b1e9d0fea64..7c66b1ba0f6 100644
--- a/test/casperjs/api-user-tests.js
+++ b/test/casperjs/api-user-tests.js
@@ -7,46 +7,46 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test the user API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-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 );
-
-// =================================================================== TESTS
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
-
- // ------------------------------------------------------------------------------------------- INDEX
- this.test.comment( 'index should get a list of users' );
- var userIndex = this.api.users.index();
- this.debug( this.jsonStr( userIndex ) );
- this.test.assert( utils.isArray( userIndex ), "index returned an array: length " + userIndex.length );
-
- // need a way to import/create a user here for testing
- if( userIndex.length <= 0 ){
- log.warn( 'No users available' );
- return;
+ // =================================================================== SET UP
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
}
- this.test.assert( userIndex.length >= 1, 'Has at least one user' );
+ spaceghost.user.loginOrRegisterUser( email, password );
- //TODO: index( deleted )
+ // =================================================================== TESTS
+ spaceghost.openHomePage().then( function(){
- // ------------------------------------------------------------------------------------------- SHOW
- this.test.comment( 'show should get detailed data about the user with the given id' );
- var userShow = this.api.users.show( userIndex[0].id );
- this.debug( this.jsonStr( userShow ) );
+ // ------------------------------------------------------------------------------------------- INDEX
+ this.test.comment( 'index should get a list of users' );
+ var userIndex = this.api.users.index();
+ this.debug( this.jsonStr( userIndex ) );
+ this.test.assert( utils.isArray( userIndex ), "index returned an array: length " + userIndex.length );
- //TODO: show( current )
- //TODO: show( deleted )
+ // need a way to import/create a user here for testing
+ if( userIndex.length <= 0 ){
+ log.warn( 'No users available' );
+ return;
+ }
+ this.test.assert( userIndex.length >= 1, 'Has at least one user' );
- // ------------------------------------------------------------------------------------------- CREATE
+ //TODO: index( deleted )
- // ------------------------------------------------------------------------------------------- MISC
-});
+ // ------------------------------------------------------------------------------------------- SHOW
+ this.test.comment( 'show should get detailed data about the user with the given id' );
+ var userShow = this.api.users.show( userIndex[0].id );
+ this.debug( this.jsonStr( userShow ) );
-// ===================================================================
+ //TODO: show( current )
+ //TODO: show( deleted )
+
+ // ------------------------------------------------------------------------------------------- CREATE
+
+ // ------------------------------------------------------------------------------------------- MISC
+ });
+
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-visualizations-tests.js b/test/casperjs/api-visualizations-tests.js
index a0099589c97..da966b48e5a 100644
--- a/test/casperjs/api-visualizations-tests.js
+++ b/test/casperjs/api-visualizations-tests.js
@@ -7,189 +7,189 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test the visualizations API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-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 );
+ // =================================================================== SET UP
+ 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 );
-// =================================================================== TESTS
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
- var ALWAYS_CREATE = true,
- indexKeys = [
- 'id', 'title', 'type', 'dbkey', 'url'
- ],
- showKeys = indexKeys.concat([
- 'user_id', 'model_class', 'revisions', 'latest_revision', 'annotation'
- ]),
- revisionKeys = [
- 'id', 'title', 'visualization_id', 'dbkey', 'model_class', 'config'
- ];
+ // =================================================================== TESTS
+ spaceghost.openHomePage().then( function(){
+ var ALWAYS_CREATE = true,
+ indexKeys = [
+ 'id', 'title', 'type', 'dbkey', 'url'
+ ],
+ showKeys = indexKeys.concat([
+ 'user_id', 'model_class', 'revisions', 'latest_revision', 'annotation'
+ ]),
+ revisionKeys = [
+ 'id', 'title', 'visualization_id', 'dbkey', 'model_class', 'config'
+ ];
- // ------------------------------------------------------------------------------------------- set up
- var visualizationIndex = this.api.visualizations.index();
- if( ALWAYS_CREATE || !visualizationIndex.length ){
- // post a visualization
- this.info( 'creating new visualization for tests' );
- var testVisualization = this.api.visualizations.create({
- title : 'Test Visualization',
+ // ------------------------------------------------------------------------------------------- set up
+ var visualizationIndex = this.api.visualizations.index();
+ if( ALWAYS_CREATE || !visualizationIndex.length ){
+ // post a visualization
+ this.info( 'creating new visualization for tests' );
+ var testVisualization = this.api.visualizations.create({
+ title : 'Test Visualization',
+ // needs to be unique
+ slug : 'test-visualization-' + Date.now(),
+ type : 'test',
+ dbkey : 'hg17',
+ annotation : 'this is a test of the emergency visualization system',
+ config : {
+ x : 10,
+ y : 12
+ }
+ });
+ this.debug( this.jsonStr( testVisualization ) );
+ }
+
+ // ------------------------------------------------------------------------------------------- INDEX
+ this.test.comment( 'index should get a list of visualizations' );
+ visualizationIndex = this.api.visualizations.index();
+ this.debug( this.jsonStr( visualizationIndex ) );
+ this.test.assert( utils.isArray( visualizationIndex ),
+ "index returned an array: length " + visualizationIndex.length );
+ this.test.assert( visualizationIndex.length >= 1, 'Has at least one visualization' );
+
+ var firstVisualization = visualizationIndex[0];
+ this.test.assert( this.hasKeys( firstVisualization, indexKeys ), 'Has the proper keys' );
+ this.test.assert( this.api.isEncodedId( firstVisualization.id ), 'Id appears well-formed' );
+
+ //TODO: index searching
+ //TODO: anon user
+ //TODO: admin user
+
+ // ------------------------------------------------------------------------------------------- SHOW
+ this.test.comment( 'show should get a visualization details object' );
+ var visualizationShow = this.api.visualizations.show( firstVisualization.id );
+ this.debug( this.jsonStr( visualizationShow ) );
+ this.test.assert( this.hasKeys( visualizationShow, showKeys ), 'Has the proper keys' );
+ this.test.assert( visualizationShow.model_class === 'Visualization',
+ 'Has the proper model_class: ' + visualizationShow.model_class );
+
+ this.test.comment( 'a visualization details object should contain an array of revision ids' );
+ var revisions = visualizationShow.revisions;
+ this.test.assert( utils.isArray( revisions ), 'revisions is an array' );
+ this.test.assert( revisions.length >= 1, 'revisions has at least one entry' );
+ var areIds = true;
+ revisions.forEach( function( revision ){
+ if( !spaceghost.api.isEncodedId( revision ) ){ areIds = false; }
+ });
+ this.test.assert( areIds, 'all revisions are ids' );
+
+ this.test.comment( 'a visualization details object should contain a subobject of the latest revision' );
+ var latestRevision = visualizationShow.latest_revision;
+ this.test.assert( utils.isObject( latestRevision ), 'latestRevision is an object' );
+ this.test.assert( this.hasKeys( latestRevision, revisionKeys ), 'latestRevision has the proper keys' );
+ this.test.assert( latestRevision.model_class === 'VisualizationRevision',
+ 'Has the proper model_class: ' + latestRevision.model_class );
+ this.test.assert( latestRevision.visualization_id === visualizationShow.id,
+ 'revision visualization_id matches containing visualization id: ' + latestRevision.visualization_id );
+ this.test.assert( visualizationShow.revisions.indexOf( latestRevision.id ) !== -1,
+ 'found latest_revision id in revisions' );
+
+ this.test.comment( 'a visualization revision should contain a subobject for the config' );
+ var config = latestRevision.config;
+ this.test.assert( utils.isObject( config ), 'config is an object:\n' + this.jsonStr( config ) );
+
+ //TODO: url in visualizationIndex == show url
+ //TODO: non existing id throws error
+ //TODO: anon user
+ //TODO: user1 has no permissions to show user2
+
+ // ------------------------------------------------------------------------------------------- CREATE
+ this.test.comment( 'Calling create should create a new visualization and allow setting the name' );
+ var visualizationData = {
+ title : 'Created Visualization',
// needs to be unique
- slug : 'test-visualization-' + Date.now(),
+ slug : 'created-visualization-' + Date.now(),
type : 'test',
dbkey : 'hg17',
- annotation : 'this is a test of the emergency visualization system',
+ annotation : 'invisible visualization',
config : {
x : 10,
y : 12
}
- });
- this.debug( this.jsonStr( testVisualization ) );
- }
+ };
+ var created = this.api.visualizations.create( visualizationData );
+ this.debug( 'returned from create:\n' + this.jsonStr( created ) );
+ this.test.assert( this.api.isEncodedId( created.id ), "create returned an id: " + created.id );
- // ------------------------------------------------------------------------------------------- INDEX
- this.test.comment( 'index should get a list of visualizations' );
- visualizationIndex = this.api.visualizations.index();
- this.debug( this.jsonStr( visualizationIndex ) );
- this.test.assert( utils.isArray( visualizationIndex ),
- "index returned an array: length " + visualizationIndex.length );
- this.test.assert( visualizationIndex.length >= 1, 'Has at least one visualization' );
+ // check v. show
+ visualizationShow = this.api.visualizations.show( created.id );
+ this.debug( 'visualizationShow:\n' + this.jsonStr( visualizationShow ) );
+ // config is re-located into a revision and won't be there
+ //this.test.assert( compareObjs( visualizationShow, visualizationData, [ 'config' ] ),
+ // "show results seem to match create data" );
- var firstVisualization = visualizationIndex[0];
- this.test.assert( this.hasKeys( firstVisualization, indexKeys ), 'Has the proper keys' );
- this.test.assert( this.api.isEncodedId( firstVisualization.id ), 'Id appears well-formed' );
+ // the following errors are produced within base.controller.UsesVisualizationsMixin._create_visualization
+ this.test.comment( 'Calling create with a non-unique slug will cause an API error' );
+ this.api.assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'visualization identifier must be unique' );
- //TODO: index searching
- //TODO: anon user
- //TODO: admin user
+ this.test.comment( 'Calling create with no title will cause an API error' );
+ visualizationData.title = '';
+ this.api.assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'visualization name is required' );
+ visualizationData.title = 'Created Visualization';
- // ------------------------------------------------------------------------------------------- SHOW
- this.test.comment( 'show should get a visualization details object' );
- var visualizationShow = this.api.visualizations.show( firstVisualization.id );
- this.debug( this.jsonStr( visualizationShow ) );
- this.test.assert( this.hasKeys( visualizationShow, showKeys ), 'Has the proper keys' );
- this.test.assert( visualizationShow.model_class === 'Visualization',
- 'Has the proper model_class: ' + visualizationShow.model_class );
+ this.test.comment( 'Calling create with improper characters in the slug will cause an API error' );
+ var oldSlug = visualizationData.slug;
+ visualizationData.slug = '123_()';
+ this.api.assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" );
+ visualizationData.slug = oldSlug;
+
+ this.test.comment( 'Calling create with an unrecognized key will cause an API error' );
+ visualizationData.bler = 'blah';
+ this.api.assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'unknown key: bler' );
+ delete visualizationData.bler;
+
+ this.test.comment( 'Calling create with an unparsable JSON config will cause an API error' );
+ visualizationData.config = '3 = nime';
+ this.api.assertRaises( function(){
+ created = this.api.visualizations.create( visualizationData );
+ }, 400, 'config must be a dictionary (JSON)' );
+
+ // ------------------------------------------------------------------------------------------ UPDATE
+ // ........................................................................................... idiot proofing
+ this.test.comment( 'updating using a nonsense key should fail with an error' );
+ this.api.assertRaises( function(){
+ returned = this.api.visualizations.update( created.id, { bler : 'blah' });
+ }, 400, 'unknown key: bler' );
+
+ this.test.comment( 'updating by attempting to change type should cause an error' );
+ this.api.assertRaises( function(){
+ returned = this.api.visualizations.update( created.id, { title : 30 });
+ }, 400, 'title must be a string or unicode' );
+ //TODO: the other types...
+
+ // ........................................................................................... title
+ //this.test.comment( 'update should create a new visualization revision' );
+ //
+ //this.test.comment( 'updating with a new title should NOT change the visualization title...' );
+ //latestRevision = this.api.visualizations.show( created.id ).latest_revision;
+ //returned = this.api.visualizations.update( created.id, {
+ // title : 'New title'
+ //});
+ //visualizationShow = this.api.visualizations.show( created.id );
+ //this.debug( this.jsonStr( visualizationShow ) );
+ //this.test.assert( visualizationShow.title === visualizationData.title,
+ // "Title does not set via update: " + visualizationShow.title );
- this.test.comment( 'a visualization details object should contain an array of revision ids' );
- var revisions = visualizationShow.revisions;
- this.test.assert( utils.isArray( revisions ), 'revisions is an array' );
- this.test.assert( revisions.length >= 1, 'revisions has at least one entry' );
- var areIds = true;
- revisions.forEach( function( revision ){
- if( !spaceghost.api.isEncodedId( revision ) ){ areIds = false; }
});
- this.test.assert( areIds, 'all revisions are ids' );
- this.test.comment( 'a visualization details object should contain a subobject of the latest revision' );
- var latestRevision = visualizationShow.latest_revision;
- this.test.assert( utils.isObject( latestRevision ), 'latestRevision is an object' );
- this.test.assert( this.hasKeys( latestRevision, revisionKeys ), 'latestRevision has the proper keys' );
- this.test.assert( latestRevision.model_class === 'VisualizationRevision',
- 'Has the proper model_class: ' + latestRevision.model_class );
- this.test.assert( latestRevision.visualization_id === visualizationShow.id,
- 'revision visualization_id matches containing visualization id: ' + latestRevision.visualization_id );
- this.test.assert( visualizationShow.revisions.indexOf( latestRevision.id ) !== -1,
- 'found latest_revision id in revisions' );
-
- this.test.comment( 'a visualization revision should contain a subobject for the config' );
- var config = latestRevision.config;
- this.test.assert( utils.isObject( config ), 'config is an object:\n' + this.jsonStr( config ) );
-
- //TODO: url in visualizationIndex == show url
- //TODO: non existing id throws error
- //TODO: anon user
- //TODO: user1 has no permissions to show user2
-
- // ------------------------------------------------------------------------------------------- CREATE
- this.test.comment( 'Calling create should create a new visualization and allow setting the name' );
- var visualizationData = {
- title : 'Created Visualization',
- // needs to be unique
- slug : 'created-visualization-' + Date.now(),
- type : 'test',
- dbkey : 'hg17',
- annotation : 'invisible visualization',
- config : {
- x : 10,
- y : 12
- }
- };
- var created = this.api.visualizations.create( visualizationData );
- this.debug( 'returned from create:\n' + this.jsonStr( created ) );
- this.test.assert( this.api.isEncodedId( created.id ), "create returned an id: " + created.id );
-
- // check v. show
- visualizationShow = this.api.visualizations.show( created.id );
- this.debug( 'visualizationShow:\n' + this.jsonStr( visualizationShow ) );
- // config is re-located into a revision and won't be there
- //this.test.assert( compareObjs( visualizationShow, visualizationData, [ 'config' ] ),
- // "show results seem to match create data" );
-
- // the following errors are produced within base.controller.UsesVisualizationsMixin._create_visualization
- this.test.comment( 'Calling create with a non-unique slug will cause an API error' );
- this.api.assertRaises( function(){
- created = this.api.visualizations.create( visualizationData );
- }, 400, 'visualization identifier must be unique' );
-
- this.test.comment( 'Calling create with no title will cause an API error' );
- visualizationData.title = '';
- this.api.assertRaises( function(){
- created = this.api.visualizations.create( visualizationData );
- }, 400, 'visualization name is required' );
- visualizationData.title = 'Created Visualization';
-
- this.test.comment( 'Calling create with improper characters in the slug will cause an API error' );
- var oldSlug = visualizationData.slug;
- visualizationData.slug = '123_()';
- this.api.assertRaises( function(){
- created = this.api.visualizations.create( visualizationData );
- }, 400, "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" );
- visualizationData.slug = oldSlug;
-
- this.test.comment( 'Calling create with an unrecognized key will cause an API error' );
- visualizationData.bler = 'blah';
- this.api.assertRaises( function(){
- created = this.api.visualizations.create( visualizationData );
- }, 400, 'unknown key: bler' );
- delete visualizationData.bler;
-
- this.test.comment( 'Calling create with an unparsable JSON config will cause an API error' );
- visualizationData.config = '3 = nime';
- this.api.assertRaises( function(){
- created = this.api.visualizations.create( visualizationData );
- }, 400, 'config must be a dictionary (JSON)' );
-
- // ------------------------------------------------------------------------------------------ UPDATE
- // ........................................................................................... idiot proofing
- this.test.comment( 'updating using a nonsense key should fail with an error' );
- this.api.assertRaises( function(){
- returned = this.api.visualizations.update( created.id, { bler : 'blah' });
- }, 400, 'unknown key: bler' );
-
- this.test.comment( 'updating by attempting to change type should cause an error' );
- this.api.assertRaises( function(){
- returned = this.api.visualizations.update( created.id, { title : 30 });
- }, 400, 'title must be a string or unicode' );
- //TODO: the other types...
-
- // ........................................................................................... title
- //this.test.comment( 'update should create a new visualization revision' );
- //
- //this.test.comment( 'updating with a new title should NOT change the visualization title...' );
- //latestRevision = this.api.visualizations.show( created.id ).latest_revision;
- //returned = this.api.visualizations.update( created.id, {
- // title : 'New title'
- //});
- //visualizationShow = this.api.visualizations.show( created.id );
- //this.debug( this.jsonStr( visualizationShow ) );
- //this.test.assert( visualizationShow.title === visualizationData.title,
- // "Title does not set via update: " + visualizationShow.title );
-
-});
-
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/api-workflow-tests.js b/test/casperjs/api-workflow-tests.js
index 5b2fafb72cc..86e1bf8dd63 100644
--- a/test/casperjs/api-workflow-tests.js
+++ b/test/casperjs/api-workflow-tests.js
@@ -7,240 +7,238 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test the workflows API', 0, function suite( test ){
spaceghost.start();
-// =================================================================== SET UP
-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 );
-
-// =================================================================== TESTS
-var workflowJSONFilepath = 'test-data/simple_test.ga',
- workflowModelClass = 'StoredWorkflow',
- workflowSummaryKeys = [
- 'id', 'model_class', 'name', 'published', 'tags', 'url'
- ],
- workflowDetailKeys = workflowSummaryKeys.concat([
- 'inputs', 'steps'
- ]),
- stepKeys = [
- 'id', 'input_steps', 'tool_id', 'type'
- ],
- simpleBedFilepath = '../../test-data/2.bed',
- uploadedFile = null,
- workflowCreateKeys = [ 'history', 'outputs' ];
-
-
-spaceghost.thenOpen( spaceghost.baseUrl ).then( function(){
-
- // ------------------------------------------------------------------------------------------- UPLOAD
- // upload first or we have no data to test
- this.test.comment( 'upload should allow importing a new workflow given in JSON form' );
- var workflowToUpload = this.loadJSONFile( workflowJSONFilepath );
- //this.debug( this.jsonStr( workflowToUpload ) );
- var returned = this.api.workflows.upload( workflowToUpload );
-
- this.test.comment( 'upload should return a summary object of what we uploaded' );
- //this.debug( this.jsonStr( returned ) );
- this.test.assert( utils.isObject( returned ), "upload returned an object" );
- this.test.assert( this.hasKeys( returned, workflowSummaryKeys ), "upload's return has the proper keys" );
- this.test.assert( this.api.isEncodedId( returned.id ),
- "id is of the proper form: " + returned.id );
- this.test.assert( returned.model_class === workflowModelClass,
- "has the proper model_class: " + returned.model_class );
- this.test.assert( returned.name === workflowToUpload.name + ' ' + '(imported from API)',
- "has the proper, modified name: " + returned.name );
- this.test.assert( !returned.published,
- "uploaded workflow defaults to un-published: " + returned.published );
- this.test.assert( utils.isArray( returned.tags ) && returned.tags.length === 0,
- "upload returned an empty tag array: " + this.jsonStr( returned.tags ) );
- this.test.assert( returned.url === '/' + utils.format( this.api.workflows.urlTpls.show, returned.id ),
- "url matches the show url: " + returned.url );
-
-
- // ------------------------------------------------------------------------------------------- INDEX
- this.test.comment( 'index should get a list of workflows' );
- var workflowIndex = this.api.workflows.index();
- this.debug( this.jsonStr( workflowIndex ) );
- this.test.assert( utils.isArray( workflowIndex ), "index returned an array: length " + workflowIndex.length );
- this.test.assert( workflowIndex.length >= 1, "index returned at least one job" );
-
- this.test.comment( 'index should have returned an object matching the workflow uploaded' );
- var firstWorkflow = workflowIndex[0];
- this.test.assert( this.hasKeys( firstWorkflow, workflowSummaryKeys ), "index has the proper keys" );
- this.test.assert( this.api.isEncodedId( firstWorkflow.id ),
- "id is of the proper form: " + firstWorkflow.id );
- this.test.assert( firstWorkflow.model_class === workflowModelClass,
- "has the proper model_class: " + firstWorkflow.model_class );
- this.test.assert( firstWorkflow.name === workflowToUpload.name + ' ' + '(imported from API)',
- "has the proper, modified name: " + firstWorkflow.name );
- this.test.assert( !firstWorkflow.published,
- "workflow is un-published: " + firstWorkflow.published );
- this.test.assert( utils.isArray( firstWorkflow.tags ) && firstWorkflow.tags.length === 0,
- "tag array is empty: " + this.jsonStr( firstWorkflow.tags ) );
- this.test.assert( firstWorkflow.url === '/' + utils.format( this.api.workflows.urlTpls.show, firstWorkflow.id ),
- "url matches the show url: " + firstWorkflow.url );
-
-
- // ------------------------------------------------------------------------------------------- SHOW
- this.test.comment( 'show should get detailed data about the workflow with the given id' );
- var workflowShow = this.api.workflows.show( firstWorkflow.id );
- this.debug( this.jsonStr( workflowShow ) );
- this.test.assert( utils.isObject( workflowShow ), "show returned an object" );
- this.test.assert( this.hasKeys( workflowShow, workflowDetailKeys ), "show has the proper keys" );
- this.test.assert( this.api.isEncodedId( workflowShow.id ),
- "id is of the proper form: " + workflowShow.id );
- this.test.assert( workflowShow.model_class === workflowModelClass,
- "has the proper model_class: " + workflowShow.model_class );
- this.test.assert( workflowShow.name === workflowToUpload.name + ' ' + '(imported from API)',
- "has the proper, modified name: " + workflowShow.name );
- this.test.assert( !workflowShow.published,
- "workflow is un-published: " + workflowShow.published );
- this.test.assert( utils.isArray( workflowShow.tags ) && workflowShow.tags.length === 0,
- "tag array is empty: " + this.jsonStr( workflowShow.tags ) );
- this.test.assert( workflowShow.url === '/' + utils.format( this.api.workflows.urlTpls.show, workflowShow.id ),
- "url matches the show url: " + workflowShow.url );
-
- this.test.comment( 'inputs from show should be an object (and, in this case, empty)' );
- var inputs = workflowShow.inputs;
- this.debug( 'inputs:\n' + this.jsonStr( inputs ) );
- this.test.assert( utils.isObject( workflowShow.inputs ), "inputs is an object" );
- this.test.assert( this.countKeys( workflowShow.inputs ) !== 0, "inputs has keys" );
-
- this.test.comment( 'steps from show should be an object containing each tool defined as a step' );
- var steps = workflowShow.steps;
- //this.debug( 'steps:\n' + this.jsonStr( steps ) );
- this.test.assert( utils.isObject( workflowShow.steps ), "steps is an object" );
- //! ids for steps (and the keys used) are un-encoded (and in strings)
- for( var stepKey in steps ){
- if( steps.hasOwnProperty( stepKey ) ){
- // any way to match this up with the workflowToUpload?
-
- this.test.assert( utils.isString( stepKey ), "step key is a string: " + stepKey );
- var step = steps[ stepKey ];
- this.debug( 'step:\n' + this.jsonStr( step ) );
- this.test.assert( this.hasKeys( step, stepKeys ), "step has the proper keys" );
-
- this.test.assert( utils.isNumber( step.id ),
- "step id is a number: " + step.id );
- try {
- this.test.assert( parseInt( stepKey, 10 ) === step.id,
- "step id matches step key: " + step.id );
- } catch( err ){
- this.test.fail( 'couldnt parse stepKey: ' + stepKey + ',' + err );
- }
-
- this.test.assert( utils.isObject( step.input_steps ), "input_steps is an object" );
- if( this.countKeys( step.input_steps ) !== 0 ){
- this.test.assert( this.hasKeys( step.input_steps, [ 'input' ] ), "input_steps has the proper keys" );
- }
-
- if( step.type === 'tool' ){
- // check for tools in this wf with the api
- this.test.assert( utils.isString( step.tool_id ),
- "step tool_id is a string: " + step.tool_id );
- var tool_used = this.api.tools.show( step.tool_id.replace( / /g, '+' ) );
- this.debug( this.jsonStr( tool_used ) );
- this.test.assert( this.countKeys( tool_used ) !== 0, "found tool in api.tools for: " + step.tool_id );
- }
- }
+ // =================================================================== SET UP
+ 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 );
+
+ // =================================================================== TESTS
+ var workflowJSONFilepath = 'test-data/simple_test.ga',
+ workflowModelClass = 'StoredWorkflow',
+ workflowSummaryKeys = [
+ 'id', 'model_class', 'name', 'published', 'tags', 'url'
+ ],
+ workflowDetailKeys = workflowSummaryKeys.concat([
+ 'inputs', 'steps'
+ ]),
+ stepKeys = [
+ 'id', 'input_steps', 'tool_id', 'type'
+ ],
+ simpleBedFilepath = '../../test-data/2.bed',
+ uploadedId = null,
+ workflowCreateKeys = [ 'history', 'outputs' ];
+ spaceghost.openHomePage().then( function(){
- // ------------------------------------------------------------------------------------------- MISC
-});
+ // ------------------------------------------------------------------------------------------- UPLOAD
+ // upload first or we have no data to test
+ this.test.comment( 'upload should allow importing a new workflow given in JSON form' );
+ var workflowToUpload = this.loadJSONFile( workflowJSONFilepath );
+ //this.debug( this.jsonStr( workflowToUpload ) );
+ var returned = this.api.workflows.upload( workflowToUpload );
-// now run the uploaded workflow
-spaceghost.tools.uploadFile( simpleBedFilepath, function( uploadInfo ){
- uploadedFile = uploadInfo;
-});
-spaceghost.then( function(){
- var currentHistory = this.api.histories.index()[0],
- firstWorkflow = this.api.workflows.show( this.api.workflows.index()[0].id );
+ this.test.comment( 'upload should return a summary object of what we uploaded' );
+ //this.debug( this.jsonStr( returned ) );
+ this.test.assert( utils.isObject( returned ), "upload returned an object" );
+ this.test.assert( this.hasKeys( returned, workflowSummaryKeys ), "upload's return has the proper keys" );
+ this.test.assert( this.api.isEncodedId( returned.id ),
+ "id is of the proper form: " + returned.id );
+ this.test.assert( returned.model_class === workflowModelClass,
+ "has the proper model_class: " + returned.model_class );
+ this.test.assert( returned.name === workflowToUpload.name + ' ' + '(imported from API)',
+ "has the proper, modified name: " + returned.name );
+ this.test.assert( !returned.published,
+ "uploaded workflow defaults to un-published: " + returned.published );
+ this.test.assert( utils.isArray( returned.tags ) && returned.tags.length === 0,
+ "upload returned an empty tag array: " + this.jsonStr( returned.tags ) );
+ this.test.assert( returned.url === '/' + utils.format( this.api.workflows.urlTpls.show, returned.id ),
+ "url matches the show url: " + returned.url );
- //this.debug( this.jsonStr( uploadedFile ) );
- var uploadedFileId = uploadedFile.hdaElement.attributes.id.split( '-' )[1];
- this.debug( this.jsonStr( uploadedFileId ) );
- this.debug( this.jsonStr( this.api.hdas.show( currentHistory.id, uploadedFileId ) ) );
- //this.debug( this.jsonStr( firstWorkflow ) );
- // find the input step by looking for a step where input_steps == {}
- var input_step = null;
- for( var stepKey in firstWorkflow.steps ){
- if( firstWorkflow.steps.hasOwnProperty( stepKey ) ){
- var step = firstWorkflow.steps[ stepKey ];
- if( this.countKeys( step.input_steps ) === 0 ){
- input_step = stepKey;
- this.debug( 'input step: ' + this.jsonStr( step ) );
- break;
- }
- }
- }
+ // ------------------------------------------------------------------------------------------- INDEX
+ this.test.comment( 'index should get a list of workflows' );
+ var workflowIndex = this.api.workflows.index();
+ this.debug( this.jsonStr( workflowIndex ) );
+ this.test.assert( utils.isArray( workflowIndex ), "index returned an array: length " + workflowIndex.length );
+ this.test.assert( workflowIndex.length >= 1, "index returned at least one job" );
- // ------------------------------------------------------------------------------------------- CREATE
- this.test.comment( 'create should allow running an existing workflow' );
- // needs workflow_id, history, ds_map
- var executionData = {
- workflow_id : firstWorkflow.id,
- history : 'hist_id=' + currentHistory.id,
- ds_map : {}
- };
- executionData.ds_map[ input_step ] = {
- src: 'hda',
- id: uploadedFileId
- };
- var returned = this.api.workflows.create( executionData );
- this.debug( this.jsonStr( returned ) );
- this.test.assert( utils.isObject( returned ), "create returned an object" );
- this.test.assert( this.hasKeys( returned, workflowCreateKeys ), "create returned the proper keys" );
- this.test.assert( this.api.isEncodedId( returned.history ),
- "history id is proper: " + returned.history );
- this.test.assert( utils.isArray( returned.outputs ),
- "create.outputs is an array: length " + returned.outputs.length );
- this.test.assert( returned.outputs.length >= 1, "there is at least one output" );
- for( var i=0; i= 1, "there is at least one output" );
+ for( var i=0; i' + includeDeletedOptionsLabel + ' shows deleted datasets' );
- var uploadSelector = '#' + testUploadInfo.hdaElement.attributes.id;
-
- this.historyoptions.includeDeleted( function(){
- this.test.assertExists( uploadSelector,
- "Deleted HDA is in the DOM (using history options -> " + includeDeletedOptionsLabel + ")" );
- this.test.assertVisible( uploadSelector,
- "Deleted HDA is visible again (using history options -> " + includeDeletedOptionsLabel + ")" );
- });
-});
-
-spaceghost.then( function(){
- this.test.comment( 'History options->' + includeDeletedOptionsLabel + ' (again) re-hides deleted datasets' );
-
- this.historyoptions.excludeDeleted( function(){
- this.test.assertDoesntExist( '#' + testUploadInfo.hdaElement.attributes.id,
- "Deleted HDA is not in the DOM (using history options -> " + includeDeletedOptionsLabel + ")" );
- });
-});
-
-spaceghost.then( function(){
- // undelete the uploaded file
- this.historyoptions.includeDeleted( function(){
- this.historypanel.undeleteHda( '#' + testUploadInfo.hdaElement.attributes.id );
- spaceghost.debug( 'undeleted' );
+ // ------------------------------------------------------------------- set up
+ // start a new user and upload a file
+ spaceghost.user.loginOrRegisterUser( email, password );
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: filepathToUpload }, function uploadCallback( id, json ){
+ uploadId = id;
});
-});
+ // ------------------------------------------------------------------- history options menu structure
+ //NOTE: options menu should be functionally tested elsewhere
+ spaceghost.openHomePage().historypanel.waitForHdas().then( function checkHistoryOptions(){
+ this.test.comment( 'History options icon should be in place and menu should have the proper structure' );
-// ------------------------------------------------------------------- hidden hdas aren't shown
-// ------------------------------------------------------------------- history options allows showing hidden hdas
-// can't test this yet w/o a way to make hdas hidden thru the ui or api
+ // check the button and icon
+ this.test.assertExists( this.historyoptions.data.selectors.button, "Found history options button" );
+ this.test.assertVisible( this.historyoptions.data.selectors.button, "History options button is visible" );
+ this.test.assertVisible( this.historyoptions.data.selectors.buttonIcon, "History options icon is visible" );
+ // open the menu
+ this.click( this.historyoptions.data.selectors.button );
+ this.test.assertVisible( this.historyoptions.data.selectors.menu,
+ "Menu is visible when options button is clicked" );
-// ------------------------------------------------------------------- history options collapses all expanded hdas
-spaceghost.then( function(){
- this.historypanel.thenExpandHda( '#' + testUploadInfo.hdaElement.attributes.id );
-});
-spaceghost.then( function(){
- this.test.comment( 'History option collapses all expanded hdas' );
+ // check the options
+ var historyOptions = this.historyoptions.data.labels.options;
+ for( var optionKey in historyOptions ){
+ if( historyOptions.hasOwnProperty( optionKey ) ){
+ var optionLabel = historyOptions[ optionKey ];
+ this.test.assertVisible( this.historyoptions.data.selectors.optionXpathByLabelFn( optionLabel ),
+ 'Option label is visible: ' + optionLabel );
+ }
+ }
- this.historyoptions.collapseExpanded( function(){
- var uploadedSelector = '#' + testUploadInfo.hdaElement.attributes.id;
- this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
- "Body for uploaded file is not visible" );
+ // clear the menu
+ this.click( 'body' );
+ this.test.assertNotVisible( this.historyoptions.data.selectors.menu,
+ "Clicking away from the menu closes it" );
});
-});
-// ===================================================================
+ // ------------------------------------------------------------------- history options collapses all expanded hdas
+ spaceghost.then( function(){
+ this.historypanel.thenExpandHda( '#dataset-' + uploadId );
+ });
+ spaceghost.then( function(){
+ this.test.comment( 'History option collapses all expanded hdas' );
+
+ this.historyoptions.collapseExpanded( function(){
+ var uploadedSelector = '#dataset-' + uploadId;
+ this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
+ "Body for uploaded file is not visible" );
+ });
+ });
+
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/history-panel-tests.js b/test/casperjs/history-panel-tests.js
index b4089f2b26d..627b2177f7e 100644
--- a/test/casperjs/history-panel-tests.js
+++ b/test/casperjs/history-panel-tests.js
@@ -7,278 +7,265 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Testing the form of the main/current history panel', 0, function suite( test ){
spaceghost.start();
-// ===================================================================
-/* TODO:
- possibly break this file up
-*/
-// =================================================================== globals and helpers
-var email = spaceghost.user.getRandomEmail(),
- password = '123456';
-if( spaceghost.fixtureData.testUser ){
- email = spaceghost.fixtureData.testUser.email;
- password = spaceghost.fixtureData.testUser.password;
- spaceghost.info( 'Will use fixtureData.testUser: ' + email );
-}
+ // ===================================================================
+ /* TODO:
+ possibly break this file up
+ */
+ // =================================================================== globals and helpers
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+ spaceghost.info( 'Will use fixtureData.testUser: ' + email );
+ }
-// selectors and labels
-var tooltipSelector = spaceghost.data.selectors.tooltipBalloon,
- editableTextClass = spaceghost.data.selectors.editableText,
- editableTextInput = spaceghost.historypanel.data.selectors.history.nameEditableTextInput,
+ // selectors and labels
+ var tooltipSelector = spaceghost.data.selectors.tooltipBalloon,
+ editableTextClass = spaceghost.data.selectors.editableText,
+ editableTextInput = spaceghost.historypanel.data.selectors.history.nameEditableTextInput,
- nameSelector = spaceghost.historypanel.data.selectors.history.name,
- subtitleSelector = spaceghost.historypanel.data.selectors.history.subtitle,
- sizeSelector = spaceghost.historypanel.data.selectors.history.size,
- unnamedName = spaceghost.historypanel.data.text.history.newName,
- initialSizeStr = spaceghost.historypanel.data.text.history.newSize,
- tagIconSelector = spaceghost.historypanel.data.selectors.history.tagIcon,
- annoIconSelector = spaceghost.historypanel.data.selectors.history.annoIcon,
- emptyMsgSelector = spaceghost.historypanel.data.selectors.history.emptyMsg,
- emptyMsgStr = spaceghost.historypanel.data.text.history.emptyMsg,
- wrapperOkClassName = spaceghost.historypanel.data.selectors.hda.wrapper.stateClasses.ok,
- tagAreaSelector = spaceghost.historypanel.data.selectors.history.tagArea,
- annoAreaSelector = spaceghost.historypanel.data.selectors.history.annoArea,
- nameTooltip = spaceghost.historypanel.data.text.history.tooltips.name,
+ nameSelector = spaceghost.historypanel.data.selectors.history.name,
+ sizeSelector = spaceghost.historypanel.data.selectors.history.size,
+ unnamedName = spaceghost.historypanel.data.text.history.newName,
+ initialSizeStr = spaceghost.historypanel.data.text.history.newSize,
+ tagIconSelector = spaceghost.historypanel.data.selectors.history.tagIcon,
+ annoIconSelector = spaceghost.historypanel.data.selectors.history.annoIcon,
+ emptyMsgSelector = spaceghost.historypanel.data.selectors.history.emptyMsg,
+ emptyMsgStr = spaceghost.historypanel.data.text.history.emptyMsg,
+ tagAreaSelector = spaceghost.historypanel.data.selectors.history.tagArea,
+ annoAreaSelector = spaceghost.historypanel.data.selectors.history.annoArea,
+ nameTooltip = spaceghost.historypanel.data.text.history.tooltips.name,
- refreshButtonSelector = 'a#history-refresh-button',
- refreshButtonIconSelector = 'span.fa-refresh',
+ refreshButtonSelector = 'a#history-refresh-button',
+ refreshButtonIconSelector = 'span.fa-refresh';
- includeDeletedOptionsLabel = spaceghost.historyoptions.data.labels.options.includeDeleted;
-
-// local
-var newHistoryName = "Test History",
- filepathToUpload = '../../test-data/1.txt',
- historyFrameInfo = {},
- testUploadInfo = {};
+ // local
+ var newHistoryName = "Test History",
+ filepathToUpload = '../../test-data/1.txt',
+ uploadId = null;
-// =================================================================== TESTS
-// ------------------------------------------------------------------- set up
-// start a new user
-spaceghost.user.loginOrRegisterUser( email, password );
+ // =================================================================== TESTS
+ // ------------------------------------------------------------------- set up
+ // start a new user
+ spaceghost.user.loginOrRegisterUser( email, password );
-// ------------------------------------------------------------------- check structure of empty history
-spaceghost.openHomePage().historypanel.waitForHdas( function(){
- this.test.comment( 'history panel with a new, empty history should be well formed' );
+ // ------------------------------------------------------------------- check structure of empty history
+ spaceghost.openHomePage().historypanel.waitForHdas( function(){
+ this.test.comment( 'history panel with a new, empty history should be well formed' );
- this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
- this.test.assertExists( nameSelector, nameSelector + ' exists' );
- this.test.assertVisible( nameSelector, 'History name is visible' );
- this.test.assertSelectorHasText( nameSelector, unnamedName, 'History name is ' + unnamedName );
+ this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
+ this.test.assertExists( nameSelector, nameSelector + ' exists' );
+ this.test.assertVisible( nameSelector, 'History name is visible' );
+ this.test.assertSelectorHasText( nameSelector, unnamedName, 'History name is ' + unnamedName );
- this.test.comment( "history should display size and size should be: " + initialSizeStr );
- this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
- this.test.assertVisible( sizeSelector, 'History size is visible' );
- this.test.assertSelectorHasText( sizeSelector, initialSizeStr,
- 'History size has "' + initialSizeStr + '"' );
+ this.test.comment( "history should display size and size should be: " + initialSizeStr );
+ this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
+ this.test.assertVisible( sizeSelector, 'History size is visible' );
+ this.test.assertSelectorHasText( sizeSelector, initialSizeStr,
+ 'History size has "' + initialSizeStr + '"' );
- this.test.comment( "tags and annotation icons should be available" );
- this.test.assertExists( tagIconSelector, 'Tag icon button found' );
- this.test.assertExists( annoIconSelector, 'Annotation icon button found' );
+ this.test.comment( "tags and annotation icons should be available" );
+ this.test.assertExists( tagIconSelector, 'Tag icon button found' );
+ this.test.assertExists( annoIconSelector, 'Annotation icon button found' );
- this.test.comment( "A message about the current history being empty should be displayed" );
- this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
- this.test.assertVisible( emptyMsgSelector, 'Empty history message is visible' );
- this.test.assertSelectorHasText( emptyMsgSelector, emptyMsgStr,
- 'Message contains\n"' + emptyMsgStr + '":\n"' + this.fetchText( emptyMsgSelector ) + '"' );
-});
+ this.test.comment( "A message about the current history being empty should be displayed" );
+ this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
+ this.test.assertVisible( emptyMsgSelector, 'Empty history message is visible' );
+ this.test.assertSelectorHasText( emptyMsgSelector, emptyMsgStr,
+ 'Message contains\n"' + emptyMsgStr + '":\n"' + this.fetchText( emptyMsgSelector ) + '"' );
+ });
-// ------------------------------------------------------------------- name editing
-spaceghost.then( function(){
- this.test.comment( 'history panel, editing the history name' );
+ // ------------------------------------------------------------------- name editing
+ spaceghost.then( function(){
+ this.test.comment( 'history panel, editing the history name' );
- this.test.comment( 'name should have a tooltip with proper info on name editing' );
- this.hoverOver( nameSelector );
- this.test.assertExists( tooltipSelector, "Found tooltip after name hover" );
- this.test.assertSelectorHasText( tooltipSelector, nameTooltip );
- // clear the tooltip
- this.page.sendEvent( 'mousemove', -1, -1 );
+ this.test.comment( 'name should have a tooltip with proper info on name editing' );
+ this.hoverOver( nameSelector );
+ this.test.assertExists( tooltipSelector, "Found tooltip after name hover" );
+ this.test.assertSelectorHasText( tooltipSelector, nameTooltip );
+ // clear the tooltip
+ this.page.sendEvent( 'mousemove', -1, -1 );
- this.test.comment( 'name should be create an input when clicked' );
- this.assertHasClass( nameSelector, editableTextClass, "Name field classed for editable text" );
- this.click( nameSelector );
- spaceghost.debug( editableTextInput );
- this.test.assertExists( editableTextInput, "Clicking on name creates an input" );
+ this.test.comment( 'name should be create an input when clicked' );
+ this.assertHasClass( nameSelector, editableTextClass, "Name field classed for editable text" );
+ this.click( nameSelector );
+ spaceghost.debug( editableTextInput );
+ this.test.assertExists( editableTextInput, "Clicking on name creates an input" );
- this.test.comment( 'name should be editable by entering keys and pressing enter' );
- //NOTE: casperjs.sendKeys adds a click before and a selector.blur after sending - won't work here
- this.page.sendEvent( 'keypress', newHistoryName );
- this.page.sendEvent( 'keypress', this.page.event.key.Enter );
- // wait for send and re-render name
- this.wait( 1000, function(){
+ this.test.comment( 'name should be editable by entering keys and pressing enter' );
+ //NOTE: casperjs.sendKeys adds a click before and a selector.blur after sending - won't work here
+ this.page.sendEvent( 'keypress', newHistoryName );
+ this.page.sendEvent( 'keypress', this.page.event.key.Enter );
+ // wait for send and re-render name
+ this.wait( 1000, function(){
+ this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is ' + newHistoryName );
+ this.test.assertDoesntExist( editableTextInput, "Input disappears after pressing enter" );
+ });
+ });
+
+ spaceghost.then( function(){
+ this.test.comment( 'name should revert if user clicks away while editing' );
+
+ this.click( nameSelector );
+ this.page.sendEvent( 'keypress', "Woodchipper metagenomics, Fargo, ND" );
+
+ this.page.sendEvent( 'mousedown', -1, -1 );
+ this.wait( 1000, function(){
+ this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is STILL ' + newHistoryName );
+ this.test.assertDoesntExist( editableTextInput, "Input disappears after clicking away" );
+ });
+ });
+
+ spaceghost.then( function(){
+ this.test.comment( 'name should revert if user hits ESC while editing' );
+
+ this.click( nameSelector );
+ this.page.sendEvent( 'keypress', "Arsenic Bacteria" );
+
+ this.page.sendEvent( 'keypress', this.page.event.key.Escape );
+ this.wait( 1000, function(){
+ this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is STILL ' + newHistoryName );
+ this.test.assertDoesntExist( editableTextInput, "Input disappears after hitting ESC" );
+ });
+ });
+
+ // ------------------------------------------------------------------- check structure of NON empty history
+ // upload file: 1.txt
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: filepathToUpload }, function uploadCallback( id, json ){
+ uploadId = id;
+ });
+
+ spaceghost.openHomePage().then( function checkPanelStructure(){
+ this.test.comment( 'checking structure of non-empty panel' );
+
+ this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
+ this.test.assertExists( nameSelector, nameSelector + ' exists' );
+ this.test.assertVisible( nameSelector, 'History name is visible' );
this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is ' + newHistoryName );
- this.test.assertDoesntExist( editableTextInput, "Input disappears after pressing enter" );
+
+ var onetxtFilesize = require( 'fs' ).size( filepathToUpload ),
+ expectedSize = onetxtFilesize + ' bytes';
+ this.test.comment( "history should display size and size should be " + onetxtFilesize + " bytes" );
+ this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
+ this.test.assertVisible( sizeSelector, 'History size is visible' );
+ this.test.assertSelectorHasText( sizeSelector, expectedSize,
+ 'History size has "' + expectedSize + '": ' + this.fetchText( sizeSelector ).trim() );
+
+ this.test.comment( "tags and annotation icons should be available" );
+ this.test.assertExists( tagIconSelector, 'Tag icon button found' );
+ this.test.assertExists( annoIconSelector, 'Annotation icon button found' );
+
+ this.test.comment( "A message about the current history being empty should NOT be displayed" );
+ this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
+ this.test.assertNotVisible( emptyMsgSelector, 'Empty history message is NOT visible' );
});
-});
-spaceghost.then( function(){
- this.test.comment( 'name should revert if user clicks away while editing' );
+ // ------------------------------------------------------------------- tags
+ // keeping this light here - better for its own test file
+ //TODO: check tooltips
+ spaceghost.then( function openTags(){
+ this.test.comment( 'tag area should open when the history panel tag icon is clicked' );
- this.click( nameSelector );
- this.page.sendEvent( 'keypress', "Woodchipper metagenomics, Fargo, ND" );
-
- this.page.sendEvent( 'mousedown', -1, -1 );
- this.wait( 1000, function(){
- this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is STILL ' + newHistoryName );
- this.test.assertDoesntExist( editableTextInput, "Input disappears after clicking away" );
+ this.click( tagIconSelector );
+ this.wait( 1000, function(){
+ this.test.assertVisible( tagAreaSelector, 'Tag area is now displayed' );
+ });
});
-});
+ spaceghost.then( function closeAnnotation(){
+ this.test.comment( 'annotation area should close when the history panel tag icon is clicked again' );
-spaceghost.then( function(){
- this.test.comment( 'name should revert if user hits ESC while editing' );
-
- this.click( nameSelector );
- this.page.sendEvent( 'keypress', "Arsenic Bacteria" );
-
- this.page.sendEvent( 'keypress', this.page.event.key.Escape );
- this.wait( 1000, function(){
- this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is STILL ' + newHistoryName );
- this.test.assertDoesntExist( editableTextInput, "Input disappears after hitting ESC" );
+ this.click( tagIconSelector );
+ this.wait( 1000, function(){
+ this.test.assertNotVisible( tagAreaSelector, 'Tag area is now hidden' );
+ });
});
-});
-// ------------------------------------------------------------------- check structure of NON empty history
-// upload file: 1.txt
-spaceghost.tools.uploadFile( filepathToUpload, function uploadCallback( _uploadInfo ){
- this.test.comment( 'uploaded file should appear in history' );
+ // ------------------------------------------------------------------- annotation
+ // keeping this light here - better for its own test file
+ //TODO: check tooltips
+ spaceghost.then( function openAnnotation(){
+ this.test.comment( 'annotation area should open when the history panel annotation icon is clicked' );
- this.debug( 'uploaded HDA info: ' + this.jsonStr( _uploadInfo ) );
- var hasHda = _uploadInfo.hdaElement,
- hasClass = _uploadInfo.hdaElement.attributes[ 'class' ],
- hasOkClass = _uploadInfo.hdaElement.attributes[ 'class' ].indexOf( wrapperOkClassName ) !== -1;
- this.test.assert( ( hasHda && hasClass && hasOkClass ), "Uploaded file: " + _uploadInfo.filename );
- testUploadInfo = _uploadInfo;
-});
-
-spaceghost.then( function checkPanelStructure(){
- this.test.comment( 'checking structure of non-empty panel' );
-
- this.test.comment( "history name should exist, be visible, and have text " + unnamedName );
- this.test.assertExists( nameSelector, nameSelector + ' exists' );
- this.test.assertVisible( nameSelector, 'History name is visible' );
- this.test.assertSelectorHasText( nameSelector, newHistoryName, 'History name is ' + newHistoryName );
-
- var onetxtFilesize = require( 'fs' ).size( filepathToUpload ),
- expectedSize = onetxtFilesize + ' bytes';
- this.test.comment( "history should display size and size should be " + onetxtFilesize + " bytes" );
- this.test.assertExists( sizeSelector, 'Found ' + sizeSelector );
- this.test.assertVisible( sizeSelector, 'History size is visible' );
- this.test.assertSelectorHasText( sizeSelector, expectedSize,
- 'History size has "' + expectedSize + '": ' + this.fetchText( sizeSelector ).trim() );
-
- this.test.comment( "tags and annotation icons should be available" );
- this.test.assertExists( tagIconSelector, 'Tag icon button found' );
- this.test.assertExists( annoIconSelector, 'Annotation icon button found' );
-
- this.test.comment( "A message about the current history being empty should NOT be displayed" );
- this.test.assertExists( emptyMsgSelector, emptyMsgSelector + ' exists' );
- this.test.assertNotVisible( emptyMsgSelector, 'Empty history message is NOT visible' );
-});
-
-// ------------------------------------------------------------------- tags
-// keeping this light here - better for its own test file
-//TODO: check tooltips
-spaceghost.then( function openTags(){
- this.test.comment( 'tag area should open when the history panel tag icon is clicked' );
-
- this.click( tagIconSelector );
- this.wait( 1000, function(){
- this.test.assertVisible( tagAreaSelector, 'Tag area is now displayed' );
+ this.click( annoIconSelector );
+ this.wait( 1000, function(){
+ this.test.assertVisible( annoAreaSelector, 'Annotation area is now displayed' );
+ });
});
-});
-spaceghost.then( function closeAnnotation(){
- this.test.comment( 'annotation area should close when the history panel tag icon is clicked again' );
+ spaceghost.then( function closeAnnotation(){
+ this.test.comment( 'annotation area should close when the history panel tag icon is clicked again' );
- this.click( tagIconSelector );
- this.wait( 1000, function(){
- this.test.assertNotVisible( tagAreaSelector, 'Tag area is now hidden' );
+ this.click( annoIconSelector );
+ this.wait( 1000, function(){
+ this.test.assertNotVisible( annoAreaSelector, 'Annotation area is now hidden' );
+ });
});
-});
-// ------------------------------------------------------------------- annotation
-// keeping this light here - better for its own test file
-//TODO: check tooltips
-spaceghost.then( function openAnnotation(){
- this.test.comment( 'annotation area should open when the history panel annotation icon is clicked' );
+ // ------------------------------------------------------------------- refresh button
+ spaceghost.then( function refreshButton(){
+ this.test.comment( 'History panel should refresh when the history refresh icon is clicked' );
- this.click( annoIconSelector );
- this.wait( 1000, function(){
- this.test.assertVisible( annoAreaSelector, 'Annotation area is now displayed' );
+ this.test.assertExists( refreshButtonSelector, "Found refresh button" );
+ this.test.assertVisible( refreshButtonSelector, "Refresh button is visible" );
+ this.test.assertVisible( refreshButtonSelector + ' ' + refreshButtonIconSelector, "Refresh icon is visible" );
+
+ //this.assertNavigationRequested( refreshButtonHref, "History refreshed when clicking refresh icon", function(){
+ // this.click( refreshButtonSelector );
+ //});
});
-});
-spaceghost.then( function closeAnnotation(){
- this.test.comment( 'annotation area should close when the history panel tag icon is clicked again' );
- this.click( annoIconSelector );
- this.wait( 1000, function(){
- this.test.assertNotVisible( annoAreaSelector, 'Annotation area is now hidden' );
+ // ------------------------------------------------------------------- hdas can be expanded by clicking on the hda
+ // broken in webkit w/ jq 1.7
+ spaceghost.historypanel.waitForHdas( function(){
+ this.test.comment( 'HDAs can be expanded by clicking on the name' );
+ var uploadedSelector = '#dataset-' + uploadId;
+
+ this.click( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.title );
+ this.wait( 1000, function(){
+ this.test.assertVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
+ "Body for uploaded file is visible" );
+ });
});
-});
-// ------------------------------------------------------------------- refresh button
-spaceghost.then( function refreshButton(){
- this.test.comment( 'History panel should refresh when the history refresh icon is clicked' );
+ // ------------------------------------------------------------------- expanded hdas still expanded after refresh
+ spaceghost.then( function(){
+ this.test.comment( 'Expanded hdas are still expanded after a refresh' );
+ var uploadedSelector = '#dataset-' + uploadId;
- this.test.assertExists( refreshButtonSelector, "Found refresh button" );
- this.test.assertVisible( refreshButtonSelector, "Refresh button is visible" );
- this.test.assertVisible( refreshButtonSelector + ' ' + refreshButtonIconSelector, "Refresh icon is visible" );
-
- //this.assertNavigationRequested( refreshButtonHref, "History refreshed when clicking refresh icon", function(){
- // this.click( refreshButtonSelector );
- //});
-});
-
-// ------------------------------------------------------------------- hdas can be expanded by clicking on the hda name
-// broken in webkit w/ jq 1.7
-spaceghost.historypanel.waitForHdas( function(){
- this.test.comment( 'HDAs can be expanded by clicking on the name' );
- this.debug( this.jsonStr( testUploadInfo ) );
- var uploadedSelector = '#' + testUploadInfo.hdaElement.attributes.id;
-
- this.click( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.title );
- this.wait( 1000, function(){
- this.test.assertVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
- "Body for uploaded file is visible" );
+ this.click( refreshButtonSelector );
+ this.historypanel.waitForHdas( function(){
+ this.test.assertVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
+ "Body for uploaded file is visible" );
+ });
+ // this will break: webkit + jq 1.7
});
-});
-// ------------------------------------------------------------------- expanded hdas are still expanded after a refresh
-spaceghost.then( function(){
- this.test.comment( 'Expanded hdas are still expanded after a refresh' );
- var uploadedSelector = '#' + testUploadInfo.hdaElement.attributes.id;
+ // ------------------------------------------------------------------- expanded hdas collapse by clicking name again
+ spaceghost.then( function(){
+ this.test.comment( 'Expanded hdas collapse by clicking name again' );
+ var uploadedSelector = '#dataset-' + uploadId;
- this.click( refreshButtonSelector );
- this.historypanel.waitForHdas( function(){
- this.test.assertVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
- "Body for uploaded file is visible" );
+ this.click( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.title );
+ this.wait( 500, function(){
+ this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
+ "Body for uploaded file is not visible" );
+ });
});
- // this will break: webkit + jq 1.7
-});
-// ------------------------------------------------------------------- expanded hdas collapse by clicking name again
-spaceghost.then( function(){
- this.test.comment( 'Expanded hdas collapse by clicking name again' );
- var uploadedSelector = '#' + testUploadInfo.hdaElement.attributes.id;
+ // ------------------------------------------------------------------- collapsed hdas still collapsed after refresh
+ spaceghost.then( function(){
+ this.test.comment( 'collapsed hdas still collapsed after a refresh' );
+ var uploadedSelector = '#dataset-' + uploadId;
- this.click( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.title );
- this.wait( 500, function(){
- this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
- "Body for uploaded file is not visible" );
+ this.click( refreshButtonSelector );
+ this.historypanel.waitForHdas( function(){
+ this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
+ "Body for uploaded file is not visible" );
+ });
});
-});
-
-// ------------------------------------------------------------------- collapsed hdas still collapsed after a refresh
-spaceghost.then( function(){
- this.test.comment( 'collapsed hdas still collapsed after a refresh' );
- var uploadedSelector = '#' + testUploadInfo.hdaElement.attributes.id;
-
- this.click( refreshButtonSelector );
- this.historypanel.waitForHdas( function(){
- this.test.assertNotVisible( uploadedSelector + ' ' + this.historypanel.data.selectors.hda.body,
- "Body for uploaded file is not visible" );
- });
-});
-/*
-*/
-// ===================================================================
+ /*
+ */
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/casperjs/modules/api.js b/test/casperjs/modules/api.js
index 956d0f777d1..cdc8d683fa4 100644
--- a/test/casperjs/modules/api.js
+++ b/test/casperjs/modules/api.js
@@ -552,6 +552,7 @@ ToolsAPI.prototype.thenUpload = function thenUpload( historyId, options, then ){
var spaceghost = this.api.spaceghost,
uploadedId;
+ // upload via the api
spaceghost.then( function(){
var returned = this.api.tools.upload( historyId, options );
this.debug( 'returned: ' + this.jsonStr( returned ) );
@@ -559,20 +560,19 @@ ToolsAPI.prototype.thenUpload = function thenUpload( historyId, options, then ){
this.debug( 'uploadedId: ' + uploadedId );
});
- spaceghost.debug( '---------------------------------------------------------- timeout: ' + ( options.timeout || spaceghost.api.tools.DEFAULT_UPLOAD_TIMEOUT ) );
- spaceghost.debug( 'timeout: ' + options.timeout );
- spaceghost.debug( 'timeout: ' + spaceghost.api.tools.DEFAULT_UPLOAD_TIMEOUT );
+ // wait for upload/queued/running to stop
spaceghost.then( function(){
+ var hda = null;
this.waitFor(
function testHdaState(){
- var hda = spaceghost.api.hdas.show( historyId, uploadedId );
+ hda = spaceghost.api.hdas.show( historyId, uploadedId );
spaceghost.debug( spaceghost.jsonStr( hda.state ) );
return !( hda.state === 'upload' || hda.state === 'queued' || hda.state === 'running' );
},
function _then(){
spaceghost.info( 'upload finished: ' + uploadedId );
if( then ){
- then.call( spaceghost, uploadedId );
+ then.call( spaceghost, uploadedId, hda );
}
//var hda = spaceghost.api.hdas.show( historyId, uploadedId );
//spaceghost.debug( spaceghost.jsonStr( hda ) );
@@ -631,6 +631,16 @@ ToolsAPI.prototype.thenUploadMultiple = function thenUploadMultiple( historyId,
};
+/** get the current history's id, then upload there */
+ToolsAPI.prototype.thenUploadToCurrent = function thenUploadToCurrent( options, then ){
+ var spaceghost = this.api.spaceghost;
+ return spaceghost.then( function(){
+ var currentHistoryId = this.api.histories.index()[0].id;
+ spaceghost.api.tools.thenUpload( currentHistoryId, options, then );
+ });
+};
+
+
// =================================================================== WORKFLOWS
var WorkflowsAPI = function WorkflowsAPI( api ){
this.api = api;
diff --git a/test/casperjs/modules/historyoptions.js b/test/casperjs/modules/historyoptions.js
index 0e83cb4486e..5f19a257640 100644
--- a/test/casperjs/modules/historyoptions.js
+++ b/test/casperjs/modules/historyoptions.js
@@ -147,19 +147,6 @@ HistoryOptions.prototype.toggle = function toggle( optionLabel, force ){
//};
-// ------------------------------------------------------------------- check the togglable options
-// these are easy, one click options (they don't open a new page)
-/** Is 'Include Deleted Datasets' on (accrd. to the menu)?
- */
-HistoryOptions.prototype.deletedAreIncluded = function deletedAreIncluded(){
- return this.isOn( this.data.labels.options.includeDeleted );
-};
-/** Is 'Include Deleted Datasets' on (accrd. to the menu)?
- */
-HistoryOptions.prototype.hiddenAreIncluded = function hiddenAreIncluded(){
- return this.isOn( this.data.labels.options.includeHidden );
-};
-
// ------------------------------------------------------------------- options that control the hpanel
/** corresponds to history options menu: 'Collapse Expanded Datasets'
*/
@@ -170,39 +157,6 @@ HistoryOptions.prototype.collapseExpanded = function collapseExpanded( then ){
});
};
-/** set 'Include Deleted Datasets' to on
- * @param {Function} then casper step to run when option is set
- */
-HistoryOptions.prototype.includeDeleted = function includeDeleted( then ){
- this.toggle( this.data.labels.options.includeDeleted, true );
- this.spaceghost.historypanel.waitForHdas( then );
-};
-
-/** set 'Include Deleted Datasets' to off
- * @param {Function} then casper step to run when option is set
- */
-HistoryOptions.prototype.excludeDeleted = function excludeDeleted( then ){
- this.toggle( this.data.labels.options.includeDeleted, false );
- this.spaceghost.historypanel.waitForHdas( then );
-};
-
-/** set 'Include Hidden Datasets' to on
- * @param {Function} then casper step to run when option is set
- */
-HistoryOptions.prototype.includeHidden = function includeHidden( then ){
- this.toggle( this.data.labels.options.includeHidden, true );
- this.spaceghost.historypanel.waitForHdas( then );
-};
-
-/** set 'Include Hidden Datasets' to off
- * @param {Function} then casper step to run when option is set
- */
-HistoryOptions.prototype.excludeHidden = function excludeHidden( then ){
- this.toggle( this.data.labels.options.includeHidden, false );
- this.spaceghost.historypanel.waitForHdas( then );
-};
-
-
// =================================================================== SELECTORS
//TODO: data is not a very good name
HistoryOptions.prototype.data = {
@@ -232,14 +186,13 @@ HistoryOptions.prototype.data = {
datasetSecurity : "Dataset Security",
resumePausedJobs : "Resume Paused Jobs",
collapseExpanded : 'Collapse Expanded Datasets',
- includeDeleted : 'Include Deleted Datasets',
- includeHidden : 'Include Hidden Datasets',
unhideHiddenDatasets : "Unhide Hidden Datasets",
- purgeDeletedDatasets : "Purge Deleted Datasets",
+ deleteHiddenDatasets : "Delete Hidden Datasets",
showStructure : "Show Structure",
+ exportCitations : "Export Citations",
exportToFile : "Export to File",
deleteHistory : "Delete",
- deleteHistoryPermanently : "Delete Permanently",
+ //deleteHistoryPermanently : "Delete Permanently",
//Other Actions
importFromFile : "Import from File"
}
diff --git a/test/casperjs/upload-tests.js b/test/casperjs/upload-tests.js
index 8617d524474..a6fb8a54a6c 100644
--- a/test/casperjs/upload-tests.js
+++ b/test/casperjs/upload-tests.js
@@ -6,87 +6,40 @@ var require = patchRequire( require ),
spaceghost.test.begin( 'Test uploading data to a history', 0, function suite( test ){
spaceghost.start();
+ // ===================================================================
-// ===================================================================
-/* TODO:
+ var email = spaceghost.user.getRandomEmail(),
+ password = '123456';
+ if( spaceghost.fixtureData.testUser ){
+ email = spaceghost.fixtureData.testUser.email;
+ password = spaceghost.fixtureData.testUser.password;
+ }
- find a way to error on bad upload?
- general tool execution
-*/
-// =================================================================== globals and helpers
-var email = spaceghost.user.getRandomEmail(),
- password = '123456';
-if( spaceghost.fixtureData.testUser ){
- email = spaceghost.fixtureData.testUser.email;
- password = spaceghost.fixtureData.testUser.password;
-}
-
-var testUploadInfo = {};
-
-// =================================================================== TESTS
-// ------------------------------------------------------------------- start a new user
-spaceghost.user.loginOrRegisterUser( email, password ).openHomePage( function(){
- var loggedInAs = spaceghost.user.loggedInAs();
- this.test.assert( loggedInAs === email, 'loggedInAs() matches email: "' + loggedInAs + '"' );
-});
-
-// ------------------------------------------------------------------- long form
-// upload a file...
-spaceghost.then( function(){
- this.test.comment( 'Test uploading a file' );
-
- var filename = '1.txt',
- filepath = '../../test-data/' + filename;
- this.tools._uploadFile( filepath );
-
- // when an upload begins successfully main should reload with a infomessagelarge
- this.withMainPanel( function mainAfterUpload(){
- var infoInfo = this.elementInfoOrNull( this.data.selectors.messages.infolarge );
- this.test.assert( infoInfo !== null,
- "Found infomessagelarge after uploading file" );
- this.test.assert( infoInfo.text.indexOf( this.data.text.upload.success ) !== -1,
- "Found upload success message: " + this.data.text.upload.success );
-
- testUploadInfo.name = filename;
+ // ------------------------------------------------------------------- start a new user
+ spaceghost.user.loginOrRegisterUser( email, password ).openHomePage( function(){
+ var loggedInAs = spaceghost.user.loggedInAs();
+ this.test.assert( loggedInAs === email, 'loggedInAs() matches email: "' + loggedInAs + '"' );
});
-});
-// ... and move to the history panel and wait for upload to finish
-spaceghost.historypanel.waitForHda( '1.txt',
- function uploadComplete( hdaElement ){
- this.test.pass( 'Upload completed successfully for: ' + this.jsonStr( hdaElement.attributes.id ) );
- },
- function timeout( hdaElement ){
- this.debug( 'hdaElement:\n' + this.jsonStr( hdaElement ) );
- this.test.fail( 'Test timed out for upload: ' + testUploadInfo.name );
- },
- 30 * 1000
-);
-
-// ------------------------------------------------------------------- short form
-spaceghost.then( function(){
- this.test.comment( 'Test convenience function' );
-
- spaceghost.tools.uploadFile( '../../test-data/1.sam', function( uploadInfo ){
- this.test.assert( uploadInfo.hdaElement !== null, "Convenience function produced hda" );
- var state = this.historypanel.getHdaState( '#' + uploadInfo.hdaElement.attributes.id );
- this.test.assert( state === 'ok', "Convenience function produced hda in ok state" );
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: '../../test-data/1.sam' }, function( uploadedId, json ){
+ var currentHistoryId = this.api.histories.index()[0].id,
+ contents = this.api.hdas.index( currentHistoryId );
+ this.test.assert( contents.length === 1, 'found one hda in history' );
+ this.test.assert( contents[0].id === uploadedId, 'id matches' );
+ this.test.assert( contents[0].name === '1.sam', 'name matches' );
});
-});
+ spaceghost.user.logout();
-// ------------------------------------------------------------------- test conv. fn error
-/*
-//??: this error's AND waitFor()s THREE times (or more) - something to do with assertStepsRaise + waitFor
-spaceghost.then( function(){
- this.test.comment( 'testing convenience function timeout error' );
- this.assertStepsRaise( 'GalaxyError: Upload Error: timeout waiting', function(){
- spaceghost.tools.uploadFile( '../../test-data/1.sam', function( uploadInfo ){
- this.test.fail( "Convenience function did not timeout!" );
- }, 50 );
+ // ------------------------------------------------------------------- anon user
+ spaceghost.openHomePage();
+ spaceghost.api.tools.thenUploadToCurrent({ filepath: '../../test-data/1.bed' }, function( uploadedId, json ){
+ var currentHistoryId = this.api.histories.index()[0].id,
+ contents = this.api.hdas.index( currentHistoryId );
+ this.test.assert( contents.length === 1, 'found one hda in history' );
+ this.test.assert( contents[0].id === uploadedId, 'id matches' );
+ this.test.assert( contents[0].name === '1.bed', 'name matches' );
});
-});
-*/
-// ===================================================================
+ // ===================================================================
spaceghost.run( function(){ test.done(); });
});
diff --git a/test/selenium/root/Login.html b/test/selenium/root/Login.html
deleted file mode 100644
index 2b84f64ebf1..00000000000
--- a/test/selenium/root/Login.html
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-
-
-
-
-Login
-
-
-