Files
galaxy/test/unit/data/test_galaxy_mapping.py
T

1238 lines
54 KiB
Python

import collections
import os
import random
import uuid
from tempfile import NamedTemporaryFile
from typing import List
import pytest
from sqlalchemy import (
inspect,
select,
)
import galaxy.datatypes.registry
import galaxy.model
import galaxy.model.mapping as mapping
from galaxy import model
from galaxy.model.base import transaction
from galaxy.model.database_utils import create_database
from galaxy.model.metadata import MetadataTempFile
from galaxy.model.orm.util import (
add_object_to_object_session,
get_object_session,
)
from galaxy.model.security import GalaxyRBACAgent
from galaxy.objectstore import QuotaSourceMap
from galaxy.util.unittest import TestCase
datatypes_registry = galaxy.datatypes.registry.Registry()
datatypes_registry.load_datatypes()
galaxy.model.set_datatypes_registry(datatypes_registry)
DB_URI = "sqlite:///:memory:"
# docker run -e POSTGRES_USER=galaxy -p 5432:5432 -d postgres
# GALAXY_TEST_UNIT_MAPPING_URI_POSTGRES_BASE='postgresql://galaxy@localhost:5432/' pytest test/unit/data/test_galaxy_mapping.py
skip_if_not_postgres_base = pytest.mark.skipif(
not os.environ.get("GALAXY_TEST_UNIT_MAPPING_URI_POSTGRES_BASE"),
reason="GALAXY_TEST_UNIT_MAPPING_URI_POSTGRES_BASE not set",
)
PRIVATE_OBJECT_STORE_ID = "my_private_data"
class BaseModelTestCase(TestCase):
model: mapping.GalaxyModelMapping
@classmethod
def _db_uri(cls):
return DB_URI
@classmethod
def setUpClass(cls):
# Start the database and connect the mapping
cls.model = mapping.init("/tmp", cls._db_uri(), create_tables=True)
model.setup_global_object_store_for_models(MockObjectStore())
assert cls.model.engine is not None
@classmethod
def persist(cls, *args, **kwargs):
session = cls.session()
commit = kwargs.get("commit", True)
for arg in args:
session.add(arg)
if commit:
session.commit()
if kwargs.get("expunge", not commit):
cls.expunge()
return arg # Return last or only arg.
@classmethod
def session(cls):
return cls.model.session
@classmethod
def expunge(cls):
cls.model.session.flush()
cls.model.session.expunge_all()
class TestMappings(BaseModelTestCase):
def test_ratings(self):
user_email = "rater@example.com"
u = model.User(email=user_email, password="password")
self.persist(u)
def persist_and_check_rating(rating_class, item):
rating = 5
rating_association = rating_class(u, item, rating)
self.persist(rating_association)
self.expunge()
stored_rating = self.model.session.scalars(select(rating_class)).all()[0]
assert stored_rating.rating == rating
assert stored_rating.user.email == user_email
sw = model.StoredWorkflow()
add_object_to_object_session(sw, u)
sw.user = u
self.persist(sw)
persist_and_check_rating(model.StoredWorkflowRatingAssociation, sw)
h = model.History(name="History for Rating", user=u)
self.persist(h)
persist_and_check_rating(model.HistoryRatingAssociation, h)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(d1)
persist_and_check_rating(model.HistoryDatasetAssociationRatingAssociation, d1)
page = model.Page()
page.user = u
self.persist(page)
persist_and_check_rating(model.PageRatingAssociation, page)
visualization = model.Visualization()
visualization.user = u
self.persist(visualization)
persist_and_check_rating(model.VisualizationRatingAssociation, visualization)
dataset_collection = model.DatasetCollection(collection_type="paired")
history_dataset_collection = model.HistoryDatasetCollectionAssociation(collection=dataset_collection)
self.persist(history_dataset_collection)
persist_and_check_rating(model.HistoryDatasetCollectionRatingAssociation, history_dataset_collection)
library_dataset_collection = model.LibraryDatasetCollectionAssociation(collection=dataset_collection)
self.persist(library_dataset_collection)
persist_and_check_rating(model.LibraryDatasetCollectionRatingAssociation, library_dataset_collection)
def test_display_name(self):
def assert_display_name_converts_to_unicode(item, name):
assert isinstance(item.get_display_name(), str)
assert item.get_display_name() == name
ldda = model.LibraryDatasetDatasetAssociation(name="ldda_name")
assert_display_name_converts_to_unicode(ldda, "ldda_name")
hda = model.HistoryDatasetAssociation(name="hda_name")
assert_display_name_converts_to_unicode(hda, "hda_name")
history = model.History(name="history_name")
assert_display_name_converts_to_unicode(history, "history_name")
library = model.Library(name="library_name")
assert_display_name_converts_to_unicode(library, "library_name")
library_folder = model.LibraryFolder(name="library_folder")
assert_display_name_converts_to_unicode(library_folder, "library_folder")
history = model.History(name="Hello₩◎ґʟⅾ")
assert isinstance(history.name, str)
assert isinstance(history.get_display_name(), str)
assert history.get_display_name() == "Hello₩◎ґʟⅾ"
def test_hda_to_library_dataset_dataset_association(self):
model = self.model
u = self.model.User(email="mary@example.com", password="password")
h1 = model.History(name="History 1", user=u)
hda = model.HistoryDatasetAssociation(
name="hda_name", create_dataset=True, history=h1, sa_session=model.session
)
self.persist(hda)
trans = collections.namedtuple("trans", "user")
target_folder = model.LibraryFolder(name="library_folder")
ldda = hda.to_library_dataset_dataset_association(
trans=trans(user=u),
target_folder=target_folder,
)
assert target_folder.item_count == 1
assert ldda.id
assert ldda.library_dataset.id
assert ldda.library_dataset_id
assert ldda.library_dataset.library_dataset_dataset_association
assert ldda.library_dataset.library_dataset_dataset_association_id
library_dataset_id = ldda.library_dataset_id
replace_dataset = ldda.library_dataset
new_ldda = hda.to_library_dataset_dataset_association(
trans=trans(user=u), target_folder=target_folder, replace_dataset=replace_dataset
)
assert new_ldda.id != ldda.id
assert new_ldda.library_dataset_id == library_dataset_id
assert new_ldda.library_dataset.library_dataset_dataset_association_id == new_ldda.id
assert len(new_ldda.library_dataset.expired_datasets) == 1
assert new_ldda.library_dataset.expired_datasets[0] == ldda
assert target_folder.item_count == 1
def test_hda_to_library_dataset_dataset_association_fails_if_private(self):
model = self.model
u = model.User(email="mary2@example.com", password="password")
h1 = model.History(name="History 1", user=u)
hda = model.HistoryDatasetAssociation(
name="hda_name", create_dataset=True, history=h1, sa_session=model.session
)
hda.dataset.object_store_id = PRIVATE_OBJECT_STORE_ID
self.persist(hda)
trans = collections.namedtuple("trans", "user")
target_folder = model.LibraryFolder(name="library_folder")
with pytest.raises(Exception) as exec_info:
hda.to_library_dataset_dataset_association(
trans=trans(user=u),
target_folder=target_folder,
)
assert galaxy.model.CANNOT_SHARE_PRIVATE_DATASET_MESSAGE in str(exec_info.value)
def test_tags(self):
TAG_NAME = "Test Tag"
my_tag = model.Tag(name=TAG_NAME)
u = model.User(email="tagger@example.com", password="password")
self.persist(my_tag, u)
def tag_and_test(taggable_object, tag_association_class):
q = select(tag_association_class).join(model.Tag).where(model.Tag.name == TAG_NAME)
assert len(self.model.session.execute(q).all()) == 0
tag_association = tag_association_class()
tag_association.tag = my_tag
taggable_object.tags = [tag_association]
self.persist(tag_association, taggable_object)
assert len(self.model.session.execute(q).all()) == 1
sw = model.StoredWorkflow(user=u)
tag_and_test(sw, model.StoredWorkflowTagAssociation)
h = model.History(name="History for Tagging", user=u)
tag_and_test(h, model.HistoryTagAssociation)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
tag_and_test(d1, model.HistoryDatasetAssociationTagAssociation)
page = model.Page(user=u)
tag_and_test(page, model.PageTagAssociation)
visualization = model.Visualization(user=u)
tag_and_test(visualization, model.VisualizationTagAssociation)
dataset_collection = model.DatasetCollection(collection_type="paired")
history_dataset_collection = model.HistoryDatasetCollectionAssociation(collection=dataset_collection)
tag_and_test(history_dataset_collection, model.HistoryDatasetCollectionTagAssociation)
library_dataset_collection = model.LibraryDatasetCollectionAssociation(collection=dataset_collection)
tag_and_test(library_dataset_collection, model.LibraryDatasetCollectionTagAssociation)
def test_collection_get_interface(self):
u = model.User(email="mary@example.com", password="password")
h1 = model.History(name="History 1", user=u)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
c1 = model.DatasetCollection(collection_type="list")
elements = 100
dces = [
model.DatasetCollectionElement(collection=c1, element=d1, element_identifier=f"{i}", element_index=i)
for i in range(elements)
]
self.persist(u, h1, d1, c1, *dces, commit=False, expunge=False)
self.model.session.flush()
for i in range(elements):
assert c1[i] == dces[i]
def test_dataset_instance_order(self) -> None:
u = model.User(email="mary@example.com", password="password")
h1 = model.History(name="History 1", user=u)
elements = []
list_pair = model.DatasetCollection(collection_type="list:paired")
for i in range(20):
pair = model.DatasetCollection(collection_type="pair")
forward = model.HistoryDatasetAssociation(
extension="txt", history=h1, name=f"forward_{i}", create_dataset=True, sa_session=self.model.session
)
reverse = model.HistoryDatasetAssociation(
extension="bam", history=h1, name=f"reverse_{i}", create_dataset=True, sa_session=self.model.session
)
dce1 = model.DatasetCollectionElement(
collection=pair, element=forward, element_identifier=f"forward_{i}", element_index=1
)
dce2 = model.DatasetCollectionElement(
collection=pair, element=reverse, element_identifier=f"reverse_{i}", element_index=2
)
to_persist = [(forward, reverse), (dce1, dce2)]
self.persist(pair)
for pair_item in to_persist:
if i % 2:
self.persist(pair_item[0])
self.persist(pair_item[1])
else:
self.persist(pair_item[1])
self.persist(pair_item[0])
elements.append(
model.DatasetCollectionElement(
collection=list_pair, element=pair, element_index=i, element_identifier=str(i)
)
)
self.persist(list_pair)
random.shuffle(elements)
for item in elements:
self.persist(item)
forward_hdas: List[model.HistoryDatasetAssociation] = []
reverse_hdas: List[model.HistoryDatasetAssociation] = []
for i, dataset_instance in enumerate(list_pair.dataset_instances):
if i % 2:
reverse_hdas.append(dataset_instance)
else:
forward_hdas.append(dataset_instance)
assert all(d.name == f"forward_{i}" for i, d in enumerate(forward_hdas))
assert all(d.name == f"reverse_{i}" for i, d in enumerate(reverse_hdas))
def test_collections_in_histories(self):
u = model.User(email="mary@example.com", password="password")
h1 = model.History(name="History 1", user=u)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
d2 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
c1 = model.DatasetCollection(collection_type="pair")
hc1 = model.HistoryDatasetCollectionAssociation(history=h1, collection=c1, name="HistoryCollectionTest1")
dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="left")
dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="right")
self.persist(u, h1, d1, d2, c1, hc1, dce1, dce2)
stmt = (
select(model.HistoryDatasetCollectionAssociation)
.filter(model.HistoryDatasetCollectionAssociation.name == "HistoryCollectionTest1")
.limit(1)
)
loaded_dataset_collection = self.model.session.scalars(stmt).first().collection
assert len(loaded_dataset_collection.elements) == 2
assert loaded_dataset_collection.collection_type == "pair"
assert loaded_dataset_collection["left"] == dce1
assert loaded_dataset_collection["right"] == dce2
def test_collections_in_library_folders(self):
u = model.User(email="mary2@example.com", password="password")
lf = model.LibraryFolder(name="RootFolder")
library = model.Library(name="Library1", root_folder=lf)
ld1 = model.LibraryDataset()
ld2 = model.LibraryDataset()
ldda1 = model.LibraryDatasetDatasetAssociation(extension="txt", library_dataset=ld1)
ldda2 = model.LibraryDatasetDatasetAssociation(extension="txt", library_dataset=ld1)
c1 = model.DatasetCollection(collection_type="pair")
dce1 = model.DatasetCollectionElement(collection=c1, element=ldda1)
dce2 = model.DatasetCollectionElement(collection=c1, element=ldda2)
self.persist(u, library, lf, ld1, ld2, c1, ldda1, ldda2, dce1, dce2)
# TODO:
# loaded_dataset_collection = self.query( model.DatasetCollection ).filter( model.DatasetCollection.name == "LibraryCollectionTest1" ).first()
# assert len(loaded_dataset_collection.datasets) == 2
# assert loaded_dataset_collection.collection_type == "pair"
def test_dataset_action_tuples(self):
u = model.User(email="foo", password="foo")
h1 = model.History(user=u)
hda1 = model.HistoryDatasetAssociation(history=h1, create_dataset=True, sa_session=self.model.session)
hda2 = model.HistoryDatasetAssociation(history=h1, create_dataset=True, sa_session=self.model.session)
r1 = model.Role()
dp1 = model.DatasetPermissions(action="action1", dataset=hda1.dataset, role=r1)
dp2 = model.DatasetPermissions(action=None, dataset=hda1.dataset, role=r1)
dp3 = model.DatasetPermissions(action="action3", dataset=hda1.dataset, role=r1)
c1 = model.DatasetCollection(collection_type="type1")
dce1 = model.DatasetCollectionElement(collection=c1, element=hda1)
dce2 = model.DatasetCollectionElement(collection=c1, element=hda2)
self.model.session.add_all([u, h1, hda1, hda2, r1, dp1, dp2, dp3, c1, dce1, dce2])
self.model.session.flush()
assert c1.dataset_action_tuples == [("action1", r1.id), ("action3", r1.id)]
def test_nested_collection_attributes(self):
u = model.User(email="mary2@example.com", password="password")
h1 = model.History(name="History 1", user=u)
d1 = model.HistoryDatasetAssociation(
extension="bam", history=h1, create_dataset=True, sa_session=self.model.session
)
index = NamedTemporaryFile("w")
index.write("cool bam index")
index2 = NamedTemporaryFile("w")
index2.write("cool bam index 2")
metadata_dict = {
"bam_index": MetadataTempFile.from_JSON({"kwds": {}, "filename": index.name}),
"bam_csi_index": MetadataTempFile.from_JSON({"kwds": {}, "filename": index2.name}),
}
d1.metadata.from_JSON_dict(json_dict=metadata_dict)
assert d1.metadata.bam_index
assert d1.metadata.bam_csi_index
assert isinstance(d1.metadata.bam_index, model.MetadataFile)
assert isinstance(d1.metadata.bam_csi_index, model.MetadataFile)
d2 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
c1 = model.DatasetCollection(collection_type="paired")
dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0)
dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1)
c2 = model.DatasetCollection(collection_type="list:paired")
dce3 = model.DatasetCollectionElement(
collection=c2, element=c1, element_identifier="inner_list", element_index=0
)
c3 = model.DatasetCollection(collection_type="list:list")
c4 = model.DatasetCollection(collection_type="list:list:paired")
dce4 = model.DatasetCollectionElement(
collection=c4, element=c2, element_identifier="outer_list", element_index=0
)
self.model.session.add_all([d1, d2, c1, dce1, dce2, c2, dce3, c3, c4, dce4])
self.model.session.flush()
stmt = c2._build_nested_collection_attributes_stmt(
element_attributes=("element_identifier",), hda_attributes=("extension",), dataset_attributes=("state",)
)
result = self.model.session.execute(stmt).all()
assert [(r._fields) for r in result] == [
("element_identifier_0", "element_identifier_1", "extension", "state"),
("element_identifier_0", "element_identifier_1", "extension", "state"),
]
stmt = c2._build_nested_collection_attributes_stmt(
element_attributes=("element_identifier",), hda_attributes=("extension",), dataset_attributes=("state",)
)
result = self.model.session.execute(stmt).all()
assert result == [("inner_list", "forward", "bam", "new"), ("inner_list", "reverse", "txt", "new")]
stmt = c2._build_nested_collection_attributes_stmt(return_entities=(model.HistoryDatasetAssociation,))
result = self.model.session.execute(stmt).all()
assert result == [(d1,), (d2,)]
stmt = c2._build_nested_collection_attributes_stmt(
return_entities=(model.HistoryDatasetAssociation, model.Dataset)
)
result = self.model.session.execute(stmt).all()
assert result == [(d1, d1.dataset), (d2, d2.dataset)]
# Assert properties that use _get_nested_collection_attributes return correct content
assert c2.dataset_instances == [d1, d2]
assert c2.dataset_elements == [dce1, dce2]
assert c2.dataset_action_tuples == []
assert c2.populated_optimized
assert c2.dataset_states_and_extensions_summary == ({"new"}, {"txt", "bam"})
assert c2.element_identifiers_extensions_paths_and_metadata_files == [
[
("inner_list", "forward"),
"bam",
"mock_dataset_14.dat",
[("bai", "mock_dataset_14.dat"), ("bam.csi", "mock_dataset_14.dat")],
],
[("inner_list", "reverse"), "txt", "mock_dataset_14.dat", []],
]
assert c3.dataset_instances == []
assert c3.dataset_elements == []
assert c3.dataset_states_and_extensions_summary == (set(), set())
stmt = c4._build_nested_collection_attributes_stmt(element_attributes=("element_identifier",))
result = self.model.session.execute(stmt).all()
assert result == [
("outer_list", "inner_list", "forward"),
("outer_list", "inner_list", "reverse"),
]
assert c4.dataset_elements == [dce1, dce2]
def test_dataset_dbkeys_and_extensions_summary(self):
u = model.User(email="mary2@example.com", password="password")
h1 = model.History(name="History 1", user=u)
d1 = model.HistoryDatasetAssociation(
extension="bam", dbkey="hg19", history=h1, create_dataset=True, sa_session=self.model.session
)
d2 = model.HistoryDatasetAssociation(
extension="txt", dbkey="hg19", history=h1, create_dataset=True, sa_session=self.model.session
)
c1 = model.DatasetCollection(collection_type="paired")
dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0)
dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1)
hdca = model.HistoryDatasetCollectionAssociation(collection=c1, history=h1)
self.model.session.add_all([d1, d2, c1, dce1, dce2, hdca])
self.model.session.flush()
assert hdca.dataset_dbkeys_and_extensions_summary[0] == {"hg19"}
assert hdca.dataset_dbkeys_and_extensions_summary[1] == {"bam", "txt"}
def test_populated_optimized_ok(self):
u = model.User(email="mary2@example.com", password="password")
h1 = model.History(name="History 1", user=u)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
d2 = model.HistoryDatasetAssociation(
extension="txt", history=h1, create_dataset=True, sa_session=self.model.session
)
c1 = model.DatasetCollection(collection_type="paired")
dce1 = model.DatasetCollectionElement(collection=c1, element=d1, element_identifier="forward", element_index=0)
dce2 = model.DatasetCollectionElement(collection=c1, element=d2, element_identifier="reverse", element_index=1)
self.model.session.add_all([d1, d2, c1, dce1, dce2])
self.model.session.flush()
assert c1.populated
assert c1.populated_optimized
def test_populated_optimized_empty_list_list_ok(self):
c1 = model.DatasetCollection(collection_type="list")
c2 = model.DatasetCollection(collection_type="list:list")
dce1 = model.DatasetCollectionElement(
collection=c2, element=c1, element_identifier="empty_list", element_index=0
)
self.model.session.add_all([c1, c2, dce1])
self.model.session.flush()
assert c1.populated
assert c1.populated_optimized
assert c2.populated
assert c2.populated_optimized
def test_populated_optimized_list_list_not_populated(self):
c1 = model.DatasetCollection(collection_type="list")
c1.populated_state = False
c2 = model.DatasetCollection(collection_type="list:list")
dce1 = model.DatasetCollectionElement(
collection=c2, element=c1, element_identifier="empty_list", element_index=0
)
self.model.session.add_all([c1, c2, dce1])
self.model.session.flush()
assert not c1.populated
assert not c1.populated_optimized
assert not c2.populated
assert not c2.populated_optimized
def test_default_disk_usage(self):
u = model.User(email="disk_default@test.com", password="password")
self.persist(u)
u.adjust_total_disk_usage(1, None)
u_id = u.id
self.expunge()
user_reload = self.model.session.get(model.User, u_id)
assert user_reload.disk_usage == 1
def test_basic(self):
original_user_count = len(self.model.session.scalars(select(model.User)).all())
# Make some changes and commit them
u = model.User(email="james@foo.bar.baz", password="password")
h1 = model.History(name="History 1", user=u)
h2 = model.History(name=("H" * 1024))
self.persist(u, h1, h2)
metadata = dict(chromCol=1, startCol=2, endCol=3)
d1 = model.HistoryDatasetAssociation(
extension="interval", metadata=metadata, history=h2, create_dataset=True, sa_session=self.model.session
)
self.persist(d1)
# Check
users = self.model.session.scalars(select(model.User)).all()
assert len(users) == original_user_count + 1
user = [user for user in users if user.email == "james@foo.bar.baz"][0]
assert user.email == "james@foo.bar.baz"
assert user.password == "password"
assert len(user.histories) == 1
assert user.histories[0].name == "History 1"
hists = self.model.session.scalars(select(model.History)).all()
hist0 = [history for history in hists if history.id == h1.id][0]
hist1 = [history for history in hists if history.id == h2.id][0]
assert hist0.name == "History 1"
assert hist1.name == ("H" * 255)
assert hist0.user == user
assert hist1.user is None
assert hist1.datasets[0].metadata.chromCol == 1
# The filename test has moved to objectstore
# id = hist1.datasets[0].id
# assert hist1.datasets[0].file_name == os.path.join( "/tmp", *directory_hash_id( id ) ) + f"/dataset_{id}.dat"
# Do an update and check
hist1.name = "History 2b"
self.expunge()
hists = self.model.session.scalars(select(model.History)).all()
hist0 = [history for history in hists if history.name == "History 1"][0]
hist1 = [history for history in hists if history.name == "History 2b"][0]
assert hist0.name == "History 1"
assert hist1.name == "History 2b"
# gvk TODO need to ad test for GalaxySessions, but not yet sure what they should look like.
def test_metadata_spec(self):
metadata = dict(chromCol=1, startCol=2, endCol=3)
d = model.HistoryDatasetAssociation(extension="interval", metadata=metadata, sa_session=self.model.session)
assert d.metadata.chromCol == 1
assert d.metadata.anyAttribute is None
assert "items" not in d.metadata
def test_dataset_job_relationship(self):
dataset = model.Dataset()
job = model.Job()
dataset.job = job
self.persist(job, dataset)
loaded_dataset = self.model.session.execute(
select(model.Dataset).filter(model.Dataset.id == dataset.id)
).scalar_one()
assert loaded_dataset.job_id == job.id
def test_jobs(self):
u = model.User(email="jobtest@foo.bar.baz", password="password")
job = model.Job()
job.user = u
job.tool_id = "cat1"
self.persist(u, job)
loaded_job = self.model.session.scalars(select(model.Job).filter(model.Job.user == u).limit(1)).first()
assert loaded_job.tool_id == "cat1"
def test_job_metrics(self):
u = model.User(email="jobtest@foo.bar.baz", password="password")
job = model.Job()
job.user = u
job.tool_id = "cat1"
job.add_metric("gx", "galaxy_slots", 5)
job.add_metric("system", "system_name", "localhost")
self.persist(u, job)
task = model.Task(job=job, working_directory="/tmp", prepare_files_cmd="split.sh")
task.add_metric("gx", "galaxy_slots", 5)
task.add_metric("system", "system_name", "localhost")
big_value = ":".join(f"{i}" for i in range(2000))
task.add_metric("env", "BIG_PATH", big_value)
self.persist(task)
# Ensure big values truncated
assert len(task.text_metrics[1].metric_value) <= 1023
def test_tasks(self):
u = model.User(email="jobtest@foo.bar.baz", password="password")
job = model.Job()
task = model.Task(job=job, working_directory="/tmp", prepare_files_cmd="split.sh")
job.user = u
self.persist(u, job, task)
loaded_task = self.model.session.scalars(select(model.Task).filter(model.Task.job == job).limit(1)).first()
assert loaded_task.prepare_input_files_cmd == "split.sh"
def test_history_contents(self):
u = model.User(email="contents@foo.bar.baz", password="password")
# gs = model.GalaxySession()
h1 = model.History(name="HistoryContentsHistory1", user=u)
self.persist(u, h1, expunge=False)
d1 = self.new_hda(h1, name="1")
d2 = self.new_hda(h1, name="2", visible=False, object_store_id="foobar")
d3 = self.new_hda(h1, name="3", deleted=True, object_store_id="three_store")
d4 = self.new_hda(h1, name="4", visible=False, deleted=True)
self.session().flush()
def contents_iter_names(**kwds):
history = self.model.session.scalars(
select(model.History).filter(model.History.name == "HistoryContentsHistory1").limit(1)
).first()
return [hda.name for hda in history.contents_iter(**kwds)]
assert contents_iter_names() == ["1", "2", "3", "4"]
assert contents_iter_names(deleted=False) == ["1", "2"]
assert contents_iter_names(visible=True) == ["1", "3"]
assert contents_iter_names(visible=True, object_store_ids=["three_store"]) == ["3"]
assert contents_iter_names(visible=False) == ["2", "4"]
assert contents_iter_names(deleted=True, visible=False) == ["4"]
assert contents_iter_names(deleted=False, object_store_ids=["foobar"]) == ["2"]
assert contents_iter_names(deleted=False, object_store_ids=["foobar2"]) == []
assert contents_iter_names(ids=[d1.id, d2.id, d3.id, d4.id]) == ["1", "2", "3", "4"]
assert contents_iter_names(ids=[d1.id, d2.id, d3.id, d4.id], max_in_filter_length=1) == ["1", "2", "3", "4"]
assert contents_iter_names(ids=[d1.id, d3.id]) == ["1", "3"]
def test_history_audit(self):
u = model.User(email="contents@foo.bar.baz", password="password")
h1 = model.History(name="HistoryAuditHistory", user=u)
h2 = model.History(name="HistoryAuditHistory", user=u)
def get_audit_table_entries(history):
stmt = select(model.HistoryAudit.table).filter(model.HistoryAudit.table.c.history_id == history.id)
return self.session().execute(stmt).all()
def get_latest_entry(entries):
# key ensures result is correct if new columns are added
return max(entries, key=lambda x: x.update_time)
self.persist(u, h1, h2, expunge=False)
assert len(get_audit_table_entries(h1)) == 1
assert len(get_audit_table_entries(h2)) == 1
self.new_hda(h1, name="1")
self.new_hda(h2, name="2")
session = self.session()
with transaction(session):
session.commit()
# _next_hid modifies history, plus trigger on HDA means 2 additional audit rows per history
h1_audits = get_audit_table_entries(h1)
h2_audits = get_audit_table_entries(h2)
assert len(h1_audits) == 3
assert len(h2_audits) == 3
h1_latest = get_latest_entry(h1_audits)
h2_latest = get_latest_entry(h2_audits)
# In galaxy, HistoryAudit.prune() executes in the context of a separate thread, where it
# starts and commits a new transaction, closing a scoped session on exit. Thus, here we
# should end the current transaction (via rollback) and add the History objects to a new
# session, as the previous one will be closed.
session.rollback()
model.HistoryAudit.prune(session)
session.add_all([h1, h2])
h1_audits = get_audit_table_entries(h1)
h2_audits = get_audit_table_entries(h2)
assert len(h1_audits) == 1
assert len(h2_audits) == 1
assert h1_audits[0] == h1_latest
assert h2_audits[0] == h2_latest
def _non_empty_flush(self):
lf = model.LibraryFolder(name="RootFolder")
session = self.session()
session.add(lf)
session.flush()
def test_current_session(self):
user = model.User(email="testworkflows@bx.psu.edu", password="password")
galaxy_session = model.GalaxySession()
galaxy_session.user = user
self.persist(user, galaxy_session)
assert user.current_galaxy_session == galaxy_session
new_galaxy_session = model.GalaxySession()
user.galaxy_sessions.append(new_galaxy_session)
self.persist(user, new_galaxy_session)
assert user.current_galaxy_session == new_galaxy_session
def test_flush_refreshes(self):
# Normally I don't believe in unit testing library code, but the behaviors around attribute
# states and flushing in SQL Alchemy is very subtle and it is good to have a executable
# reference for how it behaves in the context of Galaxy objects.
model = self.model
user = model.User(email="testworkflows@bx.psu.edu", password="password")
galaxy_session = model.GalaxySession()
galaxy_session_other = model.GalaxySession()
galaxy_session.user = user
galaxy_session_other.user = user
self.persist(user, galaxy_session_other, galaxy_session)
galaxy_session_id = galaxy_session.id
self.expunge()
session = self.session()
galaxy_model_object = self.model.session.get(model.GalaxySession, galaxy_session_id)
expected_id = galaxy_model_object.id
# id loaded as part of the object query, could be any non-deferred attribute.
assert "id" not in inspect(galaxy_model_object).unloaded
# Perform an empty flush, verify empty flush doesn't reload all attributes.
session.flush()
assert "id" not in inspect(galaxy_model_object).unloaded
# However, flushing anything non-empty - even unrelated object will invalidate
# the session ID.
self._non_empty_flush()
if session().in_transaction():
session.commit()
assert "id" in inspect(galaxy_model_object).unloaded
# Fetch the ID loads the value from the database
assert expected_id == galaxy_model_object.id
assert "id" not in inspect(galaxy_model_object).unloaded
# Using cached_id instead does not exhibit this behavior.
self._non_empty_flush()
if session().in_transaction():
session.commit()
assert expected_id == galaxy.model.cached_id(galaxy_model_object)
assert "id" in inspect(galaxy_model_object).unloaded
# Keeping the following failed experiments here for future reference,
# I probed the internals of the attribute tracking and couldn't find an
# alternative, generalized way to get the previously loaded value for unloaded
# attributes.
# print(galaxy_model_object._sa_instance_state.attrs.id)
# print(dir(galaxy_model_object._sa_instance_state.attrs.id))
# print(galaxy_model_object._sa_instance_state.attrs.id.loaded_value)
# print(galaxy_model_object._sa_instance_state.attrs.id.state)
# print(galaxy_model_object._sa_instance_state.attrs.id.load_history())
# print(dir(galaxy_model_object._sa_instance_state.attrs.id.load_history()))
# print(galaxy_model_object._sa_instance_state.identity)
# print(dir(galaxy_model_object._sa_instance_state))
# print(galaxy_model_object._sa_instance_state.expired_attributes)
# print(galaxy_model_object._sa_instance_state.expired)
# print(galaxy_model_object._sa_instance_state._instance_dict().keys())
# print(dir(galaxy_model_object._sa_instance_state._instance_dict))
# assert False
# Verify cached_id works even immediately after an initial flush, prevents a second SELECT
# query that would be executed if object.id was used.
galaxy_model_object_new = model.GalaxySession()
session.add(galaxy_model_object_new)
session.flush()
if session().in_transaction():
session.commit()
assert galaxy.model.cached_id(galaxy_model_object_new)
assert "id" in inspect(galaxy_model_object_new).unloaded
# Verify a targeted flush prevent expiring unrelated objects.
galaxy_model_object_new.id # noqa: B018
assert "id" not in inspect(galaxy_model_object_new).unloaded
session.flush(model.GalaxySession())
assert "id" not in inspect(galaxy_model_object_new).unloaded
def test_workflows(self):
user = model.User(email="testworkflows@bx.psu.edu", password="password")
child_workflow = _workflow_from_steps(user, [])
self.persist(child_workflow)
workflow_step_1 = model.WorkflowStep()
workflow_step_1.order_index = 0
workflow_step_1.type = "data_input"
workflow_step_2 = model.WorkflowStep()
workflow_step_2.order_index = 1
workflow_step_2.type = "subworkflow"
add_object_to_object_session(workflow_step_2, child_workflow)
workflow_step_2.subworkflow = child_workflow
workflow_step_1.get_or_add_input("moo1")
workflow_step_1.get_or_add_input("moo2")
workflow_step_2.get_or_add_input("moo")
workflow_step_1.add_connection("foo", "cow", workflow_step_2)
workflow = _workflow_from_steps(user, [workflow_step_1, workflow_step_2])
self.persist(workflow)
workflow_id = workflow.id
annotation = model.WorkflowStepAnnotationAssociation()
annotation.annotation = "Test Step Annotation"
annotation.user = user
add_object_to_object_session(annotation, workflow_step_1)
annotation.workflow_step = workflow_step_1
self.persist(annotation)
assert workflow_step_1.id is not None
workflow_invocation = _invocation_for_workflow(user, workflow)
invocation_uuid = uuid.uuid1()
workflow_invocation.uuid = invocation_uuid
workflow_invocation_step1 = model.WorkflowInvocationStep()
add_object_to_object_session(workflow_invocation_step1, workflow_invocation)
workflow_invocation_step1.workflow_invocation = workflow_invocation
workflow_invocation_step1.workflow_step = workflow_step_1
subworkflow_invocation = model.WorkflowInvocation()
workflow_invocation.attach_subworkflow_invocation_for_step(workflow_step_2, subworkflow_invocation)
workflow_invocation_step2 = model.WorkflowInvocationStep()
add_object_to_object_session(workflow_invocation_step2, workflow_invocation)
workflow_invocation_step2.workflow_invocation = workflow_invocation
workflow_invocation_step2.workflow_step = workflow_step_2
h1 = workflow_invocation.history
add_object_to_object_session(workflow_invocation, h1)
d1 = self.new_hda(h1, name="1")
workflow_request_dataset = model.WorkflowRequestToInputDatasetAssociation()
add_object_to_object_session(workflow_request_dataset, workflow_invocation)
workflow_request_dataset.workflow_invocation = workflow_invocation
workflow_request_dataset.workflow_step = workflow_step_1
workflow_request_dataset.dataset = d1
self.persist(workflow_invocation)
assert workflow_request_dataset is not None
assert workflow_invocation.id is not None
history_id = h1.id
self.expunge()
loaded_invocation = self.model.session.get(model.WorkflowInvocation, workflow_invocation.id)
assert loaded_invocation.uuid == invocation_uuid, f"{loaded_invocation.uuid} != {invocation_uuid}"
assert loaded_invocation
assert loaded_invocation.history.id == history_id
# recover user after expunge
user = loaded_invocation.history.user
step_1, step_2 = loaded_invocation.workflow.steps
assert not step_1.subworkflow
assert step_2.subworkflow
assert len(loaded_invocation.steps) == 2
subworkflow_invocation_assoc = loaded_invocation.get_subworkflow_invocation_association_for_step(step_2)
assert subworkflow_invocation_assoc is not None
assert isinstance(subworkflow_invocation_assoc.subworkflow_invocation, model.WorkflowInvocation)
assert isinstance(subworkflow_invocation_assoc.parent_workflow_invocation, model.WorkflowInvocation)
assert subworkflow_invocation_assoc.subworkflow_invocation.history.id == history_id
loaded_workflow = self.model.session.get(model.Workflow, workflow_id)
assert len(loaded_workflow.steps[0].annotations) == 1
copied_workflow = loaded_workflow.copy(user=user)
annotations = copied_workflow.steps[0].annotations
assert len(annotations) == 1
stored_workflow = loaded_workflow.stored_workflow
counts = stored_workflow.invocation_counts()
assert counts
workflow_invocation_0 = _invocation_for_workflow(user, loaded_workflow)
workflow_invocation_1 = _invocation_for_workflow(user, loaded_workflow)
workflow_invocation_1.state = "scheduled"
self.model.session.add(workflow_invocation_0)
self.model.session.add(workflow_invocation_1)
# self.persist(workflow_invocation_0)
# self.persist(workflow_invocation_1)
self.model.session.flush()
counts = stored_workflow.invocation_counts()
print(counts)
assert counts.root["new"] == 2
assert counts.root["scheduled"] == 1
def test_role_creation(self):
security_agent = GalaxyRBACAgent(self.model)
def check_private_role(private_role, email):
assert private_role.type == model.Role.types.PRIVATE
assert len(private_role.users) == 1
assert private_role.name == email
assert private_role.description == "Private Role for " + email
email = "rule_user_1@example.com"
u = model.User(email=email, password="password")
self.persist(u)
role = security_agent.get_private_user_role(u)
assert role is None
role = security_agent.create_private_user_role(u)
assert role is not None
check_private_role(role, email)
email = "rule_user_2@example.com"
u = model.User(email=email, password="password")
self.persist(u)
role = security_agent.get_private_user_role(u)
assert role is None
role = security_agent.get_private_user_role(u, auto_create=True)
assert role is not None
check_private_role(role, email)
# make sure re-running auto_create doesn't break things
role = security_agent.get_private_user_role(u, auto_create=True)
assert role is not None
check_private_role(role, email)
def test_private_share_role(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, u_other = self._three_users("private_share_role")
h = model.History(name="History for Annotation", user=u_from)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
security_agent.privately_share_dataset(d1.dataset, [u_to])
assert security_agent.can_access_dataset(u_to.all_roles(), d1.dataset)
assert not security_agent.can_access_dataset(u_other.all_roles(), d1.dataset)
def test_make_dataset_public(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, u_other = self._three_users("make_dataset_public")
h = model.History(name="History for Annotation", user=u_from)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
security_agent.privately_share_dataset(d1.dataset, [u_to])
security_agent.make_dataset_public(d1.dataset)
assert security_agent.can_access_dataset(u_to.all_roles(), d1.dataset)
assert security_agent.can_access_dataset(u_other.all_roles(), d1.dataset)
def test_set_all_dataset_permissions(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, _, u_other = self._three_users("set_all_perms")
h = model.History(name="History for Annotation", user=u_from)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
role = security_agent.get_private_user_role(u_from, auto_create=True)
access_action = security_agent.permitted_actions.DATASET_ACCESS.action
manage_action = security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action
permissions = {access_action: [role], manage_action: [role]}
assert security_agent.can_access_dataset(u_other.all_roles(), d1.dataset)
security_agent.set_all_dataset_permissions(d1.dataset, permissions)
assert not security_agent.allow_action(
u_other.all_roles(), security_agent.permitted_actions.DATASET_ACCESS, d1.dataset
)
assert not security_agent.can_access_dataset(u_other.all_roles(), d1.dataset)
def test_can_manage_privately_shared_dataset(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, u_other = self._three_users("can_manage_dataset")
h = model.History(name="History for Prevent Sharing", user=u_from)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
self._make_owned(security_agent, u_from, d1)
assert security_agent.can_manage_dataset(u_from.all_roles(), d1.dataset)
security_agent.privately_share_dataset(d1.dataset, [u_to])
assert not security_agent.can_manage_dataset(u_to.all_roles(), d1.dataset)
def test_can_manage_private_dataset(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, _, u_other = self._three_users("can_manage_dataset_ps")
h = model.History(name="History for Prevent Sharing", user=u_from)
d1 = model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
self._make_private(security_agent, u_from, d1)
assert security_agent.can_manage_dataset(u_from.all_roles(), d1.dataset)
assert not security_agent.can_manage_dataset(u_other.all_roles(), d1.dataset)
def test_history_hid_counter_is_expired_after_next_hid_call(self):
u = model.User(email="hid_abuser@example.com", password="password")
h = model.History(name="History for hid testing", user=u)
self.persist(u, h)
state = inspect(h)
assert h.hid_counter == 1
assert "hid_counter" not in state.unloaded
assert "id" not in state.unloaded
h._next_hid()
assert "hid_counter" in state.unloaded # this attribute has been expired
assert "id" not in state.unloaded # but other attributes have NOT been expired
assert h.hid_counter == 2 # check this last: this causes thie hid_counter to be reloaded
def test_next_hid(self):
u = model.User(email="hid_abuser@example.com", password="password")
h = model.History(name="History for hid testing", user=u)
self.persist(u, h)
assert h.hid_counter == 1
h._next_hid()
assert h.hid_counter == 2
h._next_hid(n=3)
assert h.hid_counter == 5
def test_cannot_make_private_objectstore_dataset_public(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, _ = self._three_users("cannot_make_private_public")
h = self.model.History(name="History for Prevent Sharing", user=u_from)
d1 = self.model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
d1.dataset.object_store_id = PRIVATE_OBJECT_STORE_ID
self._make_private(security_agent, u_from, d1)
with pytest.raises(Exception) as exec_info:
self._make_owned(security_agent, u_from, d1)
assert galaxy.model.CANNOT_SHARE_PRIVATE_DATASET_MESSAGE in str(exec_info.value)
def test_cannot_make_private_objectstore_dataset_shared(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, _ = self._three_users("cannot_make_private_shared")
h = self.model.History(name="History for Prevent Sharing", user=u_from)
d1 = self.model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
d1.dataset.object_store_id = PRIVATE_OBJECT_STORE_ID
self._make_private(security_agent, u_from, d1)
with pytest.raises(Exception) as exec_info:
security_agent.privately_share_dataset(d1.dataset, [u_to])
assert galaxy.model.CANNOT_SHARE_PRIVATE_DATASET_MESSAGE in str(exec_info.value)
def test_cannot_set_dataset_permisson_on_private(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, _ = self._three_users("cannot_set_permissions_on_private")
h = self.model.History(name="History for Prevent Sharing", user=u_from)
d1 = self.model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
d1.dataset.object_store_id = PRIVATE_OBJECT_STORE_ID
self._make_private(security_agent, u_from, d1)
role = security_agent.get_private_user_role(u_to, auto_create=True)
access_action = security_agent.permitted_actions.DATASET_ACCESS.action
with pytest.raises(Exception) as exec_info:
security_agent.set_dataset_permission(d1.dataset, {access_action: [role]})
assert galaxy.model.CANNOT_SHARE_PRIVATE_DATASET_MESSAGE in str(exec_info.value)
def test_cannot_make_private_dataset_public(self):
security_agent = GalaxyRBACAgent(self.model)
u_from, u_to, u_other = self._three_users("cannot_make_private_dataset_public")
h = self.model.History(name="History for Annotation", user=u_from)
d1 = self.model.HistoryDatasetAssociation(
extension="txt", history=h, create_dataset=True, sa_session=self.model.session
)
self.persist(h, d1)
d1.dataset.object_store_id = PRIVATE_OBJECT_STORE_ID
self._make_private(security_agent, u_from, d1)
with pytest.raises(Exception) as exec_info:
security_agent.make_dataset_public(d1.dataset)
assert galaxy.model.CANNOT_SHARE_PRIVATE_DATASET_MESSAGE in str(exec_info.value)
def _three_users(self, suffix):
email_from = f"user_{suffix}e1@example.com"
email_to = f"user_{suffix}e2@example.com"
email_other = f"user_{suffix}e3@example.com"
u_from = model.User(email=email_from, password="password")
u_to = model.User(email=email_to, password="password")
u_other = model.User(email=email_other, password="password")
self.persist(u_from, u_to, u_other)
return u_from, u_to, u_other
def _make_private(self, security_agent, user, hda):
role = security_agent.get_private_user_role(user, auto_create=True)
access_action = security_agent.permitted_actions.DATASET_ACCESS.action
manage_action = security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action
permissions = {access_action: [role], manage_action: [role]}
self._set_permissions(security_agent, hda.dataset, permissions)
def _make_owned(self, security_agent, user, hda):
role = security_agent.get_private_user_role(user, auto_create=True)
manage_action = security_agent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action
permissions = {manage_action: [role]}
self._set_permissions(security_agent, hda.dataset, permissions)
def _set_permissions(self, security_agent, dataset, permissions):
# TODO: refactor set_all_dataset_permissions to actually throw an exception :|
if error := security_agent.set_all_dataset_permissions(dataset, permissions):
raise Exception(error)
def new_hda(self, history, **kwds):
object_store_id = kwds.pop("object_store_id", None)
hda = self.model.HistoryDatasetAssociation(create_dataset=True, sa_session=self.model.session, **kwds)
if object_store_id is not None:
hda.dataset.object_store_id = object_store_id
return history.add_dataset(hda)
@skip_if_not_postgres_base
class TestPostgresMappings(TestMappings):
@classmethod
def _db_uri(cls):
base = os.environ.get("GALAXY_TEST_UNIT_MAPPING_URI_POSTGRES_BASE")
dbname = "gxtest" + str(uuid.uuid4())
assert base
postgres_url = base + dbname
create_database(postgres_url)
return postgres_url
def _invocation_for_workflow(user, workflow):
h1 = galaxy.model.History(name="WorkflowHistory1", user=user)
workflow_invocation = galaxy.model.WorkflowInvocation()
workflow_invocation.workflow = workflow
workflow_invocation.history = h1
workflow_invocation.state = "new"
return workflow_invocation
def _workflow_from_steps(user, steps):
stored_workflow = galaxy.model.StoredWorkflow()
add_object_to_object_session(stored_workflow, user)
stored_workflow.user = user
workflow = galaxy.model.Workflow()
if steps:
for step in steps:
if get_object_session(step):
add_object_to_object_session(workflow, step)
break
workflow.steps = steps
workflow.stored_workflow = stored_workflow
return workflow
class MockObjectStore:
def __init__(self, quota_source_map=None):
self._quota_source_map = quota_source_map or QuotaSourceMap()
def get_quota_source_map(self):
return self._quota_source_map
def size(self, dataset):
return 42
def exists(self, *args, **kwds):
return True
def get_filename(self, *args, **kwds):
return "mock_dataset_14.dat"
def construct_path(self, *args, **kwds):
return "mock_dataset_14.dat"
def get_store_by(self, *args, **kwds):
return "id"
def update_from_file(self, *arg, **kwds):
pass
def is_private(self, object):
if object.object_store_id == PRIVATE_OBJECT_STORE_ID:
return True
else:
return False