galaxy-test-api 25.0.4__tar.gz → 25.1.dev0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/HISTORY.rst +3 -31
  2. {galaxy_test_api-25.0.4/galaxy_test_api.egg-info → galaxy_test_api-25.1.dev0}/PKG-INFO +4 -32
  3. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/_framework.py +1 -1
  4. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/conftest.py +5 -6
  5. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/sharable.py +1 -3
  6. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_dataset_collections.py +378 -18
  7. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_datasets.py +3 -7
  8. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_display_applications.py +1 -2
  9. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_drs.py +1 -2
  10. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_folder_contents.py +7 -9
  11. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_group_roles.py +1 -2
  12. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_group_users.py +1 -2
  13. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_groups.py +1 -2
  14. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_history_contents.py +54 -7
  15. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_item_tags.py +1 -2
  16. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_landing.py +69 -10
  17. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_pages.py +6 -8
  18. galaxy_test_api-25.1.dev0/galaxy_test/api/test_proxy.py +73 -0
  19. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_roles.py +2 -3
  20. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tags.py +1 -2
  21. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tool_execute.py +9 -10
  22. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tools.py +156 -41
  23. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_unprivileged_tools.py +2 -4
  24. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_workflows.py +293 -238
  25. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0/galaxy_test_api.egg-info}/PKG-INFO +4 -32
  26. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/setup.cfg +1 -1
  27. galaxy_test_api-25.0.4/galaxy_test/api/test_proxy.py +0 -205
  28. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/LICENSE +0 -0
  29. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/MANIFEST.in +0 -0
  30. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/README.rst +0 -0
  31. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/dev-requirements.txt +0 -0
  32. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/__init__.py +0 -0
  33. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/__init__.py +0 -0
  34. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/cwl/__init__.py +0 -0
  35. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/embed_test_1.gxwf.yml +0 -0
  36. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/embed_test_1_tool.gxtool.yml +0 -0
  37. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_authenticate.py +0 -0
  38. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_configuration.py +0 -0
  39. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_container_resolution.py +0 -0
  40. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_datatypes.py +0 -0
  41. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_folders.py +0 -0
  42. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_framework.py +0 -0
  43. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_histories.py +0 -0
  44. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_history_contents_provenance.py +0 -0
  45. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_jobs.py +0 -0
  46. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_libraries.py +0 -0
  47. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_library_contents.py +0 -0
  48. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_licenses.py +0 -0
  49. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_page_revisions.py +0 -0
  50. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_short_term_storage.py +0 -0
  51. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tool_data.py +0 -0
  52. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tools_upload.py +0 -0
  53. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_tours.py +0 -0
  54. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_users.py +0 -0
  55. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_visualizations.py +0 -0
  56. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_webhooks.py +0 -0
  57. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_workflow_build_module.py +0 -0
  58. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_workflow_extraction.py +0 -0
  59. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_workflows_cwl.py +0 -0
  60. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/api/test_workflows_from_yaml.py +0 -0
  61. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test/py.typed +0 -0
  62. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test_api.egg-info/SOURCES.txt +0 -0
  63. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test_api.egg-info/dependency_links.txt +0 -0
  64. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test_api.egg-info/requires.txt +0 -0
  65. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/galaxy_test_api.egg-info/top_level.txt +0 -0
  66. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/pyproject.toml +0 -0
  67. {galaxy_test_api-25.0.4 → galaxy_test_api-25.1.dev0}/test-requirements.txt +0 -0
@@ -3,39 +3,11 @@ History
3
3
 
4
4
  .. to_doc
5
5
 
6
- -------------------
7
- 25.0.4 (2025-11-18)
8
- -------------------
9
-
6
+ ---------
7
+ 25.1.dev0
8
+ ---------
10
9
 
11
- =========
12
- Bug fixes
13
- =========
14
-
15
- * Fix PUT /api/workflows for user defined tools by `@mvdbeek <https://github.com/mvdbeek>`_ in `#20987 <https://github.com/galaxyproject/galaxy/pull/20987>`_
16
- * Run landing request state through validator by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21087 <https://github.com/galaxyproject/galaxy/pull/21087>`_
17
- * Fix workflow run form input restrictions by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21095 <https://github.com/galaxyproject/galaxy/pull/21095>`_
18
- * Create new datasets when creating skipped database operation tool outputs by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21142 <https://github.com/galaxyproject/galaxy/pull/21142>`_
19
- * Fix proxy url validation for non-printable characters by `@davelopez <https://github.com/davelopez>`_ in `#21280 <https://github.com/galaxyproject/galaxy/pull/21280>`_
20
-
21
- ============
22
- Enhancements
23
- ============
24
10
 
25
- * Use job cache also for implicit conversions by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21021 <https://github.com/galaxyproject/galaxy/pull/21021>`_
26
- * Harden proxy redirect validation by `@davelopez <https://github.com/davelopez>`_ in `#21185 <https://github.com/galaxyproject/galaxy/pull/21185>`_
27
-
28
- =============
29
- Other changes
30
- =============
31
-
32
- * Backport `Fix proxy endpoint encoding` by `@davelopez <https://github.com/davelopez>`_ in `#21184 <https://github.com/galaxyproject/galaxy/pull/21184>`_
33
-
34
- -------------------
35
- 25.0.3 (2025-09-23)
36
- -------------------
37
-
38
- No recorded changes since last release
39
11
 
40
12
  -------------------
41
13
  25.0.2 (2025-08-13)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: galaxy-test-api
3
- Version: 25.0.4
3
+ Version: 25.1.dev0
4
4
  Summary: Galaxy API tests
5
5
  Home-page: https://github.com/galaxyproject/galaxy
6
6
  Author: Galaxy Project and Community
@@ -54,39 +54,11 @@ History
54
54
 
55
55
  .. to_doc
56
56
 
57
- -------------------
58
- 25.0.4 (2025-11-18)
59
- -------------------
60
-
57
+ ---------
58
+ 25.1.dev0
59
+ ---------
61
60
 
62
- =========
63
- Bug fixes
64
- =========
65
-
66
- * Fix PUT /api/workflows for user defined tools by `@mvdbeek <https://github.com/mvdbeek>`_ in `#20987 <https://github.com/galaxyproject/galaxy/pull/20987>`_
67
- * Run landing request state through validator by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21087 <https://github.com/galaxyproject/galaxy/pull/21087>`_
68
- * Fix workflow run form input restrictions by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21095 <https://github.com/galaxyproject/galaxy/pull/21095>`_
69
- * Create new datasets when creating skipped database operation tool outputs by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21142 <https://github.com/galaxyproject/galaxy/pull/21142>`_
70
- * Fix proxy url validation for non-printable characters by `@davelopez <https://github.com/davelopez>`_ in `#21280 <https://github.com/galaxyproject/galaxy/pull/21280>`_
71
-
72
- ============
73
- Enhancements
74
- ============
75
61
 
76
- * Use job cache also for implicit conversions by `@mvdbeek <https://github.com/mvdbeek>`_ in `#21021 <https://github.com/galaxyproject/galaxy/pull/21021>`_
77
- * Harden proxy redirect validation by `@davelopez <https://github.com/davelopez>`_ in `#21185 <https://github.com/galaxyproject/galaxy/pull/21185>`_
78
-
79
- =============
80
- Other changes
81
- =============
82
-
83
- * Backport `Fix proxy endpoint encoding` by `@davelopez <https://github.com/davelopez>`_ in `#21184 <https://github.com/galaxyproject/galaxy/pull/21184>`_
84
-
85
- -------------------
86
- 25.0.3 (2025-09-23)
87
- -------------------
88
-
89
- No recorded changes since last release
90
62
 
91
63
  -------------------
92
64
  25.0.2 (2025-08-13)
@@ -1,5 +1,5 @@
1
+ from collections.abc import Iterator
1
2
  from typing import (
2
- Iterator,
3
3
  Optional,
4
4
  )
5
5
  from unittest import SkipTest
@@ -1,11 +1,10 @@
1
1
  """Fixtures for a version of API testing that relies more heavily on pytest injection."""
2
2
 
3
3
  import os
4
+ from collections.abc import Iterator
4
5
  from dataclasses import dataclass
5
6
  from typing import (
6
7
  Any,
7
- Iterator,
8
- List,
9
8
  Optional,
10
9
  )
11
10
 
@@ -130,8 +129,8 @@ def target_history(
130
129
 
131
130
  @pytest.fixture
132
131
  def required_tools(
133
- dataset_populator: DatasetPopulator, history_id: str, required_tool_ids: List[str]
134
- ) -> List[RequiredTool]:
132
+ dataset_populator: DatasetPopulator, history_id: str, required_tool_ids: list[str]
133
+ ) -> list[RequiredTool]:
135
134
  tools = []
136
135
  for tool_id in required_tool_ids:
137
136
  tool = RequiredTool(dataset_populator, tool_id, history_id)
@@ -140,7 +139,7 @@ def required_tools(
140
139
 
141
140
 
142
141
  @pytest.fixture
143
- def required_tool(dataset_populator: DatasetPopulator, history_id: str, required_tool_ids: List[str]) -> RequiredTool:
142
+ def required_tool(dataset_populator: DatasetPopulator, history_id: str, required_tool_ids: list[str]) -> RequiredTool:
144
143
  if len(required_tool_ids) != 1:
145
144
  raise AssertionError("required_tool fixture must only be used on methods that require a single tool")
146
145
  tool_id = required_tool_ids[0]
@@ -162,7 +161,7 @@ def check_required_tools(anonymous_galaxy_interactor, request):
162
161
 
163
162
 
164
163
  @pytest.fixture
165
- def required_tool_ids(anonymous_galaxy_interactor, request) -> List[str]:
164
+ def required_tool_ids(anonymous_galaxy_interactor, request) -> list[str]:
166
165
  tool_ids = []
167
166
  for marker in request.node.iter_markers():
168
167
  if marker.name == "requires_tool_id":
@@ -1,7 +1,5 @@
1
1
  from typing import (
2
2
  Any,
3
- Dict,
4
- List,
5
3
  )
6
4
  from unittest import SkipTest
7
5
  from uuid import uuid4
@@ -63,7 +61,7 @@ class SharingApiTests(UsesApiTestCaseMixin):
63
61
  sharing_response = self._get_resource_sharing_status(resource_id)
64
62
  assert not sharing_response["users_shared_with"]
65
63
 
66
- payload: Dict[str, List[str]] = {"user_ids": []}
64
+ payload: dict[str, list[str]] = {"user_ids": []}
67
65
  sharing_response = self._set_resource_sharing(resource_id, action="share_with_users", payload=payload)
68
66
  assert not sharing_response["users_shared_with"]
69
67
 
@@ -1,11 +1,17 @@
1
1
  import json
2
2
  import zipfile
3
3
  from io import BytesIO
4
- from typing import List
4
+ from pathlib import Path
5
5
  from urllib.parse import quote
6
6
 
7
+ from galaxy.schema.schema import SampleSheetColumnDefinitions
8
+ from galaxy.util import galaxy_root_path
7
9
  from galaxy.util.unittest_utils import skip_if_github_down
8
- from galaxy_test.base.api_asserts import assert_object_id_error
10
+ from galaxy_test.base.api_asserts import (
11
+ assert_has_key,
12
+ assert_object_id_error,
13
+ assert_status_code_is,
14
+ )
9
15
  from galaxy_test.base.decorators import requires_new_user
10
16
  from galaxy_test.base.populators import (
11
17
  DatasetCollectionPopulator,
@@ -13,6 +19,32 @@ from galaxy_test.base.populators import (
13
19
  )
14
20
  from ._framework import ApiTestCase
15
21
 
22
+ # copy of unit test definition in test_sample_sheet_workbook.py - maybe just serialize it as JSON?
23
+ TEST_COLUMN_DEFINITIONS_1: SampleSheetColumnDefinitions = [
24
+ {
25
+ "name": "replicate number",
26
+ "type": "int",
27
+ "description": "The replicate number of this sample.",
28
+ "default_value": 0,
29
+ "optional": False,
30
+ },
31
+ {
32
+ "name": "treatment",
33
+ "type": "string",
34
+ "restrictions": ["treatment1", "treatment2", "none"],
35
+ "description": "The treatment code for this sample.",
36
+ "default_value": "none",
37
+ "optional": False,
38
+ },
39
+ {
40
+ "name": "is control?",
41
+ "type": "boolean",
42
+ "description": "Was this sample a control? If TRUE, please ensure treatment is set to none.",
43
+ "default_value": True,
44
+ "optional": False,
45
+ },
46
+ ]
47
+
16
48
 
17
49
  class TestDatasetCollectionsApi(ApiTestCase):
18
50
  dataset_populator: DatasetPopulator
@@ -220,6 +252,249 @@ class TestDatasetCollectionsApi(ApiTestCase):
220
252
  create_response = self._post("dataset_collections", payload)
221
253
  self._assert_status_code_is(create_response, 400)
222
254
 
255
+ def test_sample_sheet_column_definition_problems(self, history_id):
256
+ contents = [
257
+ ("sample1", "1\t2\t3"),
258
+ ("sample2", "4\t5\t6"),
259
+ ]
260
+ sample_sheet_identifiers = self.dataset_collection_populator.list_identifiers(history_id, contents)
261
+ payload = dict(
262
+ name="my cool sample sheet",
263
+ instance_type="history",
264
+ history_id=history_id,
265
+ element_identifiers=sample_sheet_identifiers,
266
+ collection_type="sample_sheet",
267
+ column_definitions=[{"type": "int", "name": "replicate", "optional": False}],
268
+ rows={"sample1": [42], "sample2": [45]},
269
+ )
270
+ create_response = self._post("dataset_collections", payload, json=True)
271
+ self._check_create_response(create_response)
272
+ payload["column_definitions"] = [{"type": "intx"}]
273
+ create_response = self._post("dataset_collections", payload, json=True)
274
+ assert_status_code_is(create_response, 400)
275
+ payload["column_definitions"] = [{"typex": "int"}]
276
+ create_response = self._post("dataset_collections", payload, json=True)
277
+ assert_status_code_is(create_response, 400)
278
+ payload["column_definitions"] = [{"type": "int", "restrictions": "wrongtype", "name": "replicate"}]
279
+ create_response = self._post("dataset_collections", payload, json=True)
280
+ assert_status_code_is(create_response, 400)
281
+ payload["column_definitions"] = [
282
+ {"type": "int", "name": "replicate", "validators": [{"type": "expression", "expression": "False"}]}
283
+ ]
284
+ create_response = self._post("dataset_collections", payload, json=True)
285
+ assert_status_code_is(create_response, 400)
286
+
287
+ def test_sample_sheet_element_identifier_column_type(self, history_id):
288
+ contents = [
289
+ ("sample1", "1\t2\t3"),
290
+ ("sample2", "4\t5\t6"),
291
+ ]
292
+ sample_sheet_identifiers = self.dataset_collection_populator.list_identifiers(history_id, contents)
293
+ payload = dict(
294
+ name="my cool sample sheet",
295
+ instance_type="history",
296
+ history_id=history_id,
297
+ element_identifiers=sample_sheet_identifiers,
298
+ collection_type="sample_sheet",
299
+ column_definitions=[{"type": "element_identifier", "name": "matched_element", "optional": False}],
300
+ rows={"sample1": ["sample2"], "sample2": ["sample1"]},
301
+ )
302
+ create_response = self._post("dataset_collections", payload, json=True)
303
+ self._check_create_response(create_response)
304
+
305
+ # should not allow collection creation if element identifiers are not matching
306
+ payload["rows"] = {"sample1": ["noinsamplesheet"], "sample2": ["noinsamplesheet"]}
307
+ create_response = self._post("dataset_collections", payload, json=True)
308
+ assert_status_code_is(create_response, 400)
309
+
310
+ def test_sample_sheet_of_pairs_creation(self, history_id):
311
+ contents = [
312
+ "1\t2\t3",
313
+ "4\t5\t6",
314
+ ]
315
+ pair_identifiers = self.dataset_collection_populator.pair_identifiers(history_id, contents)
316
+ identifiers = [
317
+ {
318
+ "name": "sample1",
319
+ "collection_type": "paired",
320
+ "src": "new_collection",
321
+ "element_identifiers": pair_identifiers,
322
+ }
323
+ ]
324
+ payload = dict(
325
+ name="my cool sample sheet",
326
+ instance_type="history",
327
+ history_id=history_id,
328
+ element_identifiers=identifiers,
329
+ collection_type="sample_sheet:paired",
330
+ column_definitions=[{"type": "int", "name": "replicate", "default_value": 0, "optional": False}],
331
+ rows={"sample1": [42]},
332
+ )
333
+ create_response = self._post("dataset_collections", payload, json=True)
334
+ print(create_response.json())
335
+ self._check_create_response(create_response)
336
+ dataset_collection = create_response.json()
337
+ assert dataset_collection["collection_type"] == "sample_sheet:paired"
338
+ assert dataset_collection["name"] == "my cool sample sheet"
339
+ returned_collections = dataset_collection["elements"]
340
+ assert len(returned_collections) == 1, dataset_collection
341
+ sheet_row_0_element = returned_collections[0]
342
+ self._assert_has_keys(sheet_row_0_element, "element_index", "columns")
343
+ columns = sheet_row_0_element["columns"]
344
+ assert len(columns) == 1
345
+ assert columns[0] == 42
346
+
347
+ hdca_id = dataset_collection["id"]
348
+ dataset_collection_url = f"/api/dataset_collections/{hdca_id}"
349
+ dataset_collection = self._get(dataset_collection_url).json()
350
+ assert dataset_collection["id"] == hdca_id
351
+ assert dataset_collection["collection_type"] == "sample_sheet:paired"
352
+ assert dataset_collection["column_definitions"] is not None
353
+
354
+ def test_sample_sheet_validating_against_column_definition(self, history_id):
355
+ contents = [
356
+ ("sample1", "1\t2\t3"),
357
+ ("sample2", "4\t5\t6"),
358
+ ]
359
+ sample_sheet_identifiers = self.dataset_collection_populator.list_identifiers(history_id, contents)
360
+ payload = dict(
361
+ name="my cool sample sheet",
362
+ instance_type="history",
363
+ history_id=history_id,
364
+ element_identifiers=sample_sheet_identifiers,
365
+ collection_type="sample_sheet",
366
+ column_definitions=[{"type": "int", "name": "replicate", "default_value": 0, "optional": False}],
367
+ rows={"sample1": [42], "sample2": [45]},
368
+ )
369
+ create_response = self._post("dataset_collections", payload, json=True)
370
+ print(create_response.json())
371
+ self._check_create_response(create_response)
372
+ # now the datatype of the row data is wrong....
373
+ payload["column_definitions"] = [
374
+ {"type": "string", "name": "replicate", "default_value": "", "optional": False}
375
+ ]
376
+ create_response = self._post("dataset_collections", payload, json=True)
377
+ assert_status_code_is(create_response, 400)
378
+ print(create_response.json())
379
+
380
+ # now the row values are too small for the supplied validator
381
+ payload["column_definitions"] = [
382
+ {"type": "int", "name": "replicate", "validators": [{"type": "in_range", "min": 60}]}
383
+ ]
384
+ create_response = self._post("dataset_collections", payload, json=True)
385
+ assert_status_code_is(create_response, 400)
386
+
387
+ def test_sample_sheet_requires_columns(self, history_id):
388
+ contents = [
389
+ ("sample1", "1\t2\t3"),
390
+ ("sample2", "4\t5\t6"),
391
+ ]
392
+ sample_sheet_identifiers = self.dataset_collection_populator.list_identifiers(history_id, contents)
393
+ payload = dict(
394
+ name="my cool sample sheet",
395
+ instance_type="history",
396
+ history_id=history_id,
397
+ element_identifiers=sample_sheet_identifiers,
398
+ collection_type="sample_sheet",
399
+ column_definitions=[{"type": "int", "name": "replicate", "optional": False}],
400
+ rows={"sample1": [42], "sample2": [45]},
401
+ )
402
+ create_response = self._post("dataset_collections", payload, json=True)
403
+ dataset_collection = self._check_create_response(create_response)
404
+
405
+ self._assert_has_keys(dataset_collection, "collection_type", "column_definitions")
406
+ column_definitions = dataset_collection["column_definitions"]
407
+ assert len(column_definitions) == 1
408
+ self._assert_has_keys(column_definitions[0], "type")
409
+ assert column_definitions[0]["type"] == "int"
410
+
411
+ # TODO: restore assertion and test before merging...
412
+ # assert something about column definition here....
413
+ assert dataset_collection["collection_type"] == "sample_sheet"
414
+ assert dataset_collection["name"] == "my cool sample sheet"
415
+ returned_collections = dataset_collection["elements"]
416
+ assert len(returned_collections) == 2, dataset_collection
417
+ sheet_row_0_element = returned_collections[0]
418
+ self._assert_has_keys(sheet_row_0_element, "element_index", "columns")
419
+ record_pos_0_object = sheet_row_0_element["object"]
420
+ self._assert_has_keys(record_pos_0_object, "name", "history_content_type")
421
+ row_0 = sheet_row_0_element["columns"]
422
+ assert row_0[0] == 42
423
+
424
+ sheet_row_1_element = returned_collections[1]
425
+ self._assert_has_keys(sheet_row_1_element, "element_index", "columns")
426
+ row_1 = sheet_row_1_element["columns"]
427
+ assert row_1[0] == 45
428
+ # TODO: test case where column definition does not match supplied data
429
+
430
+ def test_workbook_download(self):
431
+ xlsx_file = self.dataset_collection_populator.download_workbook(
432
+ "sample_sheet",
433
+ [
434
+ {"name": "condition", "type": "string", "default_value": "", "optional": False},
435
+ {"name": "replicate", "type": "int", "default_value": 0, "optional": False},
436
+ ],
437
+ )
438
+ self._assert_file_looks_like_xlsx(xlsx_file)
439
+
440
+ def test_workbook_download_for_collection(self):
441
+ with self.dataset_populator.test_history(require_new=False) as history_id:
442
+ hdca_id = self.dataset_collection_populator.create_list_in_history(
443
+ history_id, contents=[("sample1", "sample1 contents")], wait=True
444
+ ).json()["outputs"][0]["id"]
445
+
446
+ xlsx_file = self.dataset_collection_populator.download_workbook_for_collection(
447
+ hdca_id,
448
+ [
449
+ {"name": "condition", "type": "string", "default_value": "", "optional": False},
450
+ {"name": "replicate", "type": "int", "default_value": 0, "optional": False},
451
+ ],
452
+ )
453
+ self._assert_file_looks_like_xlsx(xlsx_file)
454
+
455
+ def _assert_file_looks_like_xlsx(self, xlsx_file: str):
456
+ # Check the file header
457
+ with open(xlsx_file, "rb") as file:
458
+ header = file.read(4)
459
+ # The ZIP file signature is 0x50 0x4B 0x03 0x04
460
+ return header == b"\x50\x4b\x03\x04"
461
+
462
+ def test_workbook_parse(self):
463
+ xlsx_path = Path(galaxy_root_path) / "lib" / "galaxy" / "model" / "unittest_utils" / "filled_in_workbook_1.xlsx"
464
+ example_as_bytes = xlsx_path.read_bytes()
465
+ response = self.dataset_collection_populator.parse_workbook(
466
+ example_as_bytes, "sample_sheet", TEST_COLUMN_DEFINITIONS_1
467
+ )
468
+ assert_has_key(response, "rows")
469
+ rows = response["rows"]
470
+ assert rows[0]["url"] == "https://zenodo.org/records/3263975/files/DRR000770.fastqsanger.gz"
471
+ assert rows[0]["replicate number"] == 1
472
+ assert rows[0]["treatment"] == "treatment1"
473
+ assert rows[0]["is control?"] is False
474
+ assert rows[1]["replicate number"] == 2
475
+ assert rows[1]["treatment"] == "treatment1"
476
+
477
+ def test_workbook_parse_for_collection(self):
478
+ with self.dataset_populator.test_history(require_new=False) as history_id:
479
+ hdca_id = self.dataset_collection_populator.create_list_in_history(
480
+ history_id, contents=[("sample1", "sample1 contents")], wait=True
481
+ ).json()["outputs"][0]["id"]
482
+
483
+ xlsx_path = (
484
+ Path(galaxy_root_path)
485
+ / "lib"
486
+ / "galaxy"
487
+ / "model"
488
+ / "unittest_utils"
489
+ / "filled_in_workbook_from_collection.xlsx"
490
+ )
491
+ example_as_bytes = xlsx_path.read_bytes()
492
+ response = self.dataset_collection_populator.parse_workflow_for_collection(
493
+ hdca_id, example_as_bytes, TEST_COLUMN_DEFINITIONS_1
494
+ )
495
+ assert_has_key(response, "rows")
496
+ assert_has_key(response, "elements")
497
+
223
498
  def test_list_download(self):
224
499
  with self.dataset_populator.test_history(require_new=False) as history_id:
225
500
  fetch_response = self.dataset_collection_populator.create_list_in_history(
@@ -401,7 +676,7 @@ class TestDatasetCollectionsApi(ApiTestCase):
401
676
  "__files": {"files_0|file_data": open(self.test_data_resolver.get_filename("4.bed"))},
402
677
  }
403
678
  self.dataset_populator.fetch(payload)
404
- hdca = self._assert_one_collection_created_in_history(history_id)
679
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
405
680
  assert hdca["name"] == "Test upload"
406
681
  hdca_tags = hdca["tags"]
407
682
  assert len(hdca_tags) == 1
@@ -431,19 +706,19 @@ class TestDatasetCollectionsApi(ApiTestCase):
431
706
  "__files": {"files_0|file_data": open(self.test_data_resolver.get_filename("4.bed"))},
432
707
  }
433
708
  self.dataset_populator.fetch(payload)
434
- hdca = self._assert_one_collection_created_in_history(history_id)
709
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
435
710
  assert hdca["name"] == "Test upload"
436
711
  assert len(hdca["elements"]) == 1, hdca
437
712
  element0 = hdca["elements"][0]
438
713
  assert element0["element_identifier"] == "samp1"
439
714
 
440
- @skip_if_github_down
441
715
  def test_upload_collection_from_url(self):
442
716
  with self.dataset_populator.test_history(require_new=False) as history_id:
443
717
  elements = [
444
718
  {
445
719
  "src": "url",
446
- "url": "https://raw.githubusercontent.com/galaxyproject/galaxy/dev/test-data/4.bed",
720
+ "url": self.dataset_populator.base64_url_for_string("hello world"),
721
+ "name": "hello.txt",
447
722
  "info": "my cool bed",
448
723
  }
449
724
  ]
@@ -459,11 +734,11 @@ class TestDatasetCollectionsApi(ApiTestCase):
459
734
  "targets": targets,
460
735
  }
461
736
  self.dataset_populator.fetch(payload)
462
- hdca = self._assert_one_collection_created_in_history(history_id)
737
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
463
738
  assert len(hdca["elements"]) == 1, hdca
464
739
  element0 = hdca["elements"][0]
465
- assert element0["element_identifier"] == "4.bed"
466
- assert element0["object"]["file_size"] == 61
740
+ assert element0["element_identifier"] == "hello.txt"
741
+ assert element0["object"]["file_size"] == 11
467
742
 
468
743
  def test_upload_collection_deferred(self):
469
744
  with self.dataset_populator.test_history(require_new=False) as history_id:
@@ -487,7 +762,7 @@ class TestDatasetCollectionsApi(ApiTestCase):
487
762
  "targets": targets,
488
763
  }
489
764
  self.dataset_populator.fetch(payload)
490
- hdca = self._assert_one_collection_created_in_history(history_id)
765
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
491
766
  assert len(hdca["elements"]) == 1, hdca
492
767
  element0 = hdca["elements"][0]
493
768
  assert element0["element_identifier"] == "4.bed"
@@ -511,21 +786,62 @@ class TestDatasetCollectionsApi(ApiTestCase):
511
786
  "targets": targets,
512
787
  }
513
788
  self.dataset_populator.fetch(payload, assert_ok=False, wait=True)
514
- hdca = self._assert_one_collection_created_in_history(history_id)
789
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
515
790
  assert hdca["populated"] is False
516
791
  assert "bagit.txt" in hdca["populated_state_message"], hdca
517
792
 
793
+ def test_upload_flat_sample_sheet(self):
794
+ upload_flat_sample_sheet(self.dataset_populator)
795
+
796
+ def test_upload_sample_sheet_paired(self):
797
+ column_definitions = [{"type": "int", "name": "replicate", "optional": False, "default_value": 0}]
798
+ with self.dataset_populator.test_history(require_new=False) as history_id:
799
+ elements = [
800
+ {
801
+ "name": "sample1",
802
+ "row": [42],
803
+ "elements": [
804
+ {
805
+ "src": "url",
806
+ "url": self.dataset_populator.base64_url_for_string("hello world forward"),
807
+ "info": "my cool hello world forward",
808
+ "name": "forward",
809
+ },
810
+ {
811
+ "src": "url",
812
+ "url": self.dataset_populator.base64_url_for_string("hello world reverse"),
813
+ "info": "my cool hello world reverse",
814
+ "name": "forward",
815
+ },
816
+ ],
817
+ }
818
+ ]
819
+ targets = [
820
+ {
821
+ "destination": {"type": "hdca"},
822
+ "elements": elements,
823
+ "collection_type": "sample_sheet:paired",
824
+ "column_definitions": column_definitions,
825
+ }
826
+ ]
827
+ payload = {
828
+ "history_id": history_id,
829
+ "targets": targets,
830
+ }
831
+ self.dataset_populator.fetch(payload)
832
+ hdca = assert_one_collection_created_in_history(self.dataset_populator, history_id)
833
+ assert len(hdca["elements"]) == 1, hdca
834
+ element0 = hdca["elements"][0]
835
+ assert element0["element_identifier"] == "sample1"
836
+ assert element0["columns"][0] == 42
837
+
518
838
  def _assert_one_collection_created_in_history(self, history_id: str):
519
- contents_response = self._get(f"histories/{history_id}/contents/dataset_collections")
520
- self._assert_status_code_is(contents_response, 200)
521
- contents = contents_response.json()
839
+ contents = self.dataset_populator.get_history_contents_of_type(history_id, "dataset_collections")
522
840
  assert len(contents) == 1
523
841
  hdca = contents[0]
524
842
  assert hdca["history_content_type"] == "dataset_collection"
525
843
  hdca_id = hdca["id"]
526
- collection_response = self._get(f"histories/{history_id}/contents/dataset_collections/{hdca_id}")
527
- self._assert_status_code_is(collection_response, 200)
528
- return collection_response.json()
844
+ return self.dataset_populator.get_history_collection_details(history_id, content_id=hdca_id)
529
845
 
530
846
  def _check_create_response(self, create_response):
531
847
  self._assert_status_code_is(create_response, 200)
@@ -728,7 +1044,7 @@ class TestDatasetCollectionsApi(ApiTestCase):
728
1044
  self._assert_status_code_is(response, 200)
729
1045
  hdca_list_id = response.json()["outputs"][0]["id"]
730
1046
  converters = self._get("dataset_collections/" + hdca_list_id + "/suitable_converters")
731
- actual: List[str] = []
1047
+ actual: list[str] = []
732
1048
  for converter in converters.json():
733
1049
  actual.append(converter["tool_id"])
734
1050
  assert actual == []
@@ -798,3 +1114,47 @@ class TestDatasetCollectionsApi(ApiTestCase):
798
1114
  assert "contents_url" in matches[0]
799
1115
 
800
1116
  return matches[0]["contents_url"]
1117
+
1118
+
1119
+ def upload_flat_sample_sheet(dataset_populator: DatasetPopulator):
1120
+ column_definitions = [{"type": "int", "name": "replicate", "optional": False, "default_value": 0}]
1121
+ with dataset_populator.test_history(require_new=False) as history_id:
1122
+ elements = [
1123
+ {
1124
+ "src": "url",
1125
+ "url": dataset_populator.base64_url_for_string("hello world"),
1126
+ "info": "my cool hello world",
1127
+ "name": "sample1",
1128
+ "row": [42],
1129
+ }
1130
+ ]
1131
+ targets = [
1132
+ {
1133
+ "destination": {"type": "hdca"},
1134
+ "elements": elements,
1135
+ "collection_type": "sample_sheet",
1136
+ "column_definitions": column_definitions,
1137
+ }
1138
+ ]
1139
+ payload = {
1140
+ "history_id": history_id,
1141
+ "targets": targets,
1142
+ }
1143
+ dataset_populator.fetch(payload)
1144
+ hdca = assert_one_collection_created_in_history(dataset_populator, history_id)
1145
+ assert len(hdca["elements"]) == 1, hdca
1146
+ element0 = hdca["elements"][0]
1147
+ assert element0["element_identifier"] == "sample1"
1148
+ assert element0["columns"][0] == 42
1149
+ object0 = element0["object"]
1150
+ assert object0["state"] == "ok"
1151
+ assert hdca["column_definitions"] is not None
1152
+
1153
+
1154
+ def assert_one_collection_created_in_history(dataset_populator: DatasetPopulator, history_id: str):
1155
+ contents = dataset_populator.get_history_contents_of_type(history_id, "dataset_collections")
1156
+ assert len(contents) == 1
1157
+ hdca = contents[0]
1158
+ assert hdca["history_content_type"] == "dataset_collection"
1159
+ hdca_id = hdca["id"]
1160
+ return dataset_populator.get_history_collection_details(history_id, content_id=hdca_id)