mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-11-02 11:48:57 +08:00
Strip whitespace from target filename before building the Content-Disposition header to prevent h11 LocalProtocolError when dataset names contain trailing spaces. Fixes GALAXY-MAIN-4KSCZZZ0015E3
223 lines
6.5 KiB
Python
223 lines
6.5 KiB
Python
import errno
|
|
import os
|
|
import tempfile
|
|
from enum import Enum
|
|
from io import StringIO
|
|
from pathlib import Path
|
|
from typing import Dict
|
|
|
|
import pytest
|
|
|
|
from galaxy import util
|
|
from galaxy.util.json import safe_loads
|
|
|
|
SECTION_XML = """<?xml version="1.0" ?>
|
|
<section id="fasta_fastq_manipulation" name="Fasta Fastq Manipulation" version="">
|
|
<tool file="toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/fb1313d79396/seq_filter_by_id/tools/seq_filter_by_id/seq_filter_by_id.xml" guid="toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/seq_filter_by_id/0.2.5">
|
|
<tool_shed>
|
|
toolshed.g2.bx.psu.edu
|
|
</tool_shed>
|
|
</tool>
|
|
</section>
|
|
"""
|
|
|
|
|
|
def test_strip_control_characters():
|
|
s = "\x00bla"
|
|
assert util.strip_control_characters(s) == "bla"
|
|
|
|
|
|
def test_parse_xml_string():
|
|
section = util.parse_xml_string(SECTION_XML)
|
|
_verify_section(section)
|
|
|
|
|
|
def test_parse_xml_file():
|
|
with tempfile.NamedTemporaryFile(mode="w") as tmp:
|
|
tmp.write(SECTION_XML)
|
|
tmp.flush()
|
|
section = util.parse_xml(tmp.name).getroot()
|
|
_verify_section(section)
|
|
|
|
|
|
def _verify_section(section):
|
|
tool = next(iter(section))
|
|
assert sorted(tool.items()) == [
|
|
(
|
|
"file",
|
|
"toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/fb1313d79396/seq_filter_by_id/tools/seq_filter_by_id/seq_filter_by_id.xml",
|
|
),
|
|
("guid", "toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/seq_filter_by_id/0.2.5"),
|
|
]
|
|
assert next(iter(tool)).text == "toolshed.g2.bx.psu.edu"
|
|
|
|
|
|
def test_xml_to_string():
|
|
section = util.parse_xml_string(SECTION_XML)
|
|
s = util.xml_to_string(section)
|
|
assert len(s.split("\n")) == 1
|
|
|
|
|
|
def test_xml_to_string_pretty():
|
|
section = util.parse_xml_string(SECTION_XML)
|
|
s = util.xml_to_string(section, pretty=True)
|
|
PRETTY = """<?xml version="1.0" ?>
|
|
<section id="fasta_fastq_manipulation" name="Fasta Fastq Manipulation" version="">
|
|
<tool file="toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/fb1313d79396/seq_filter_by_id/tools/seq_filter_by_id/seq_filter_by_id.xml" guid="toolshed.g2.bx.psu.edu/repos/peterjc/seq_filter_by_id/seq_filter_by_id/0.2.5">
|
|
<tool_shed>toolshed.g2.bx.psu.edu</tool_shed>
|
|
</tool>
|
|
</section>"""
|
|
assert s == PRETTY
|
|
|
|
|
|
def test_parse_xml_enoent():
|
|
with tempfile.NamedTemporaryFile() as temp:
|
|
path = temp.name
|
|
with pytest.raises(IOError) as excinfo:
|
|
util.parse_xml(path)
|
|
assert excinfo.value.errno == errno.ENOENT
|
|
|
|
|
|
def test_clean_multiline_string():
|
|
x = util.clean_multiline_string("""
|
|
a
|
|
b
|
|
c
|
|
""")
|
|
assert x == "a\nb\nc\n"
|
|
|
|
|
|
def test_iter_start_of_lines():
|
|
assert list(util.iter_start_of_line(StringIO("\n1\n\n12\n123\n1234\n"), 1)) == ["\n", "1", "\n", "1", "1", "1"]
|
|
|
|
|
|
def test_safe_loads():
|
|
d: Dict[str, str] = {}
|
|
rval = safe_loads(d)
|
|
assert rval == d
|
|
assert rval is not d
|
|
rval["foo"] = "bar"
|
|
assert "foo" not in d
|
|
s = '{"foo": "bar"}'
|
|
assert safe_loads(s) == {"foo": "bar"}
|
|
|
|
|
|
def test_in_packages(monkeypatch):
|
|
util_path = Path(util.__file__).parent
|
|
assert util.in_packages() == (not str(util_path).endswith("lib/galaxy/util"))
|
|
|
|
|
|
def test_galaxy_directory(monkeypatch):
|
|
galaxy_dir = util.galaxy_directory()
|
|
assert os.path.isabs(galaxy_dir)
|
|
assert os.path.isfile(os.path.join(galaxy_dir, "run.sh"))
|
|
|
|
|
|
def test_listify() -> None:
|
|
assert util.listify(None) == []
|
|
assert util.listify(False) == []
|
|
assert util.listify(True) == [True]
|
|
assert util.listify("foo") == ["foo"]
|
|
assert util.listify("foo, bar") == ["foo", " bar"]
|
|
assert util.listify("foo, bar", do_strip=True) == ["foo", "bar"]
|
|
list_ = [1, 2, 3]
|
|
assert util.listify(list_) is list_
|
|
assert util.listify((1, 2, 3)) == [1, 2, 3]
|
|
s = {1, 2, 3}
|
|
assert util.listify(s) == [s]
|
|
d = {"a": 1, "b": 2, "c": 3}
|
|
assert util.listify(d) == [d]
|
|
o = object()
|
|
assert util.listify(o) == [o]
|
|
|
|
|
|
def test_enum_values():
|
|
class Stuff(str, Enum):
|
|
A = "a"
|
|
C = "c"
|
|
B = "b"
|
|
|
|
assert util.enum_values(Stuff) == ["a", "c", "b"]
|
|
|
|
|
|
DOI_VALID_VALUES = [
|
|
"https://doi.org/10.1234/42",
|
|
"doi.org/10.1234/42",
|
|
"doi:10.1234/42",
|
|
"doi:10.1234567890/42", # longer prefix
|
|
"doi:10.1234/42ab:%&*$//crazy-suffix/%/&/",
|
|
"doi:10.1234/aa",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("input", DOI_VALID_VALUES)
|
|
def test_validate_doi_pass(input):
|
|
assert util.validate_doi(input)
|
|
|
|
|
|
DOI_INVALID_VALUES = [
|
|
"http://doi.org/10.1234/42",
|
|
"invalid:10.1234/42",
|
|
"doi:11.1234/42",
|
|
"doi:101234/42",
|
|
"doi:10. 1234/42",
|
|
"doi:10.abc/42",
|
|
"doi:10.1234/ 42",
|
|
"doi:10.1234/42/a b",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("input", DOI_INVALID_VALUES)
|
|
def test_validate_doi_fail(input):
|
|
assert not util.validate_doi(input)
|
|
|
|
|
|
def test_validate_doi_fail_too_long():
|
|
long_suffix = "a" * (util.DOI_MAX_LENGTH - 12)
|
|
doi = f"doi:10.1000/{long_suffix}"
|
|
assert util.validate_doi(doi)
|
|
assert not util.validate_doi(doi + "a") # Increase length by 1 past max limit
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"input_name,expected_output",
|
|
[
|
|
# Existing documented behavior
|
|
("My Cool Object", "My-Cool-Object"),
|
|
("!My Cool Object!", "My-Cool-Object"),
|
|
("Hello\u20a9\u25ce\u0491\u029f\u217e", "Hello"),
|
|
# Additional edge cases
|
|
("simple", "simple"),
|
|
("UPPERCASE", "UPPERCASE"), # Note: lowercase applied separately
|
|
("with-dash", "with-dash"),
|
|
("with spaces", "with-spaces"),
|
|
(" multiple spaces ", "-multiple-spaces"),
|
|
("trailing!", "trailing"),
|
|
("!leading", "leading"),
|
|
("special@#$chars", "specialchars"),
|
|
("parentheses(test)", "parenthesestest"),
|
|
("Rincewind (Ankh-Morpork)", "Rincewind-Ankh-Morpork"),
|
|
],
|
|
)
|
|
def test_ready_name_for_url(input_name, expected_output):
|
|
"""Test that ready_name_for_url correctly sanitizes names for URL use."""
|
|
assert util.ready_name_for_url(input_name) == expected_output
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"target,expected_substring",
|
|
[
|
|
("normal.txt", 'filename="normal.txt"'),
|
|
("file.gz ", 'filename="file.gz"'),
|
|
(" file.gz", 'filename="file.gz"'),
|
|
(" file.gz ", 'filename="file.gz"'),
|
|
("Galaxy102-[name].fastqsanger.gz ", 'filename="Galaxy102-[name].fastqsanger.gz"'),
|
|
],
|
|
)
|
|
def test_to_content_disposition(target, expected_substring):
|
|
result = util.to_content_disposition(target)
|
|
assert result.startswith("attachment; ")
|
|
assert expected_substring in result
|
|
# Ensure no trailing whitespace in the header value
|
|
assert result == result.strip()
|