Skip to content

Commit f39758f

Browse files
committed
improving test cases
1 parent 7c05cdc commit f39758f

1 file changed

Lines changed: 96 additions & 77 deletions

File tree

packages/google-api-core/tests/unit/test_path_template.py

Lines changed: 96 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -675,98 +675,117 @@ def helper_test_transcode(http_options_list, expected_result_list):
675675
return (http_options, expected_result)
676676

677677

678-
def test_path_traversal_dots_validation_star():
679-
# 1. Dots in values matched to *
680-
# Single-segment positional variable * with exactly '.' or '..'
681-
with pytest.raises(
682-
ValueError, match="Invalid value \\. for positional variable\\."
683-
):
684-
path_template.expand("/v1/*", ".")
685-
with pytest.raises(
686-
ValueError, match="Invalid value \\.\\. for positional variable\\."
687-
):
688-
path_template.expand("/v1/*", "..")
689-
690-
# Named variable matching * with exactly '.' or '..'
691-
with pytest.raises(ValueError, match="Invalid value \\.\\. for region\\."):
692-
path_template.expand(
678+
@pytest.mark.parametrize(
679+
"tmpl, args, kwargs, expected_err_match",
680+
[
681+
# Positional * with . or ..
682+
[
683+
"/v1/*",
684+
[
685+
".",
686+
],
687+
{},
688+
"Invalid value \\. for positional variable\\.",
689+
],
690+
[
691+
"/v1/*",
692+
[
693+
"..",
694+
],
695+
{},
696+
"Invalid value \\.\\. for positional variable\\.",
697+
],
698+
# Named matching * with . or ..
699+
[
693700
"/compute/v1/projects/{project}/regions/{region}/addresses",
694-
project="my-project",
695-
region="..",
696-
)
697-
698-
# Sub-template named variable where a segment matching * is exactly '.' or '..'
699-
with pytest.raises(
700-
ValueError,
701-
match="Invalid value projects/my-project/locations/\\.\\. for parent\\.",
702-
):
703-
path_template.expand(
701+
[],
702+
{"project": "my-project", "region": ".."},
703+
"Invalid value \\.\\. for region\\.",
704+
],
705+
[
706+
"/compute/v1/projects/{project}/regions/{region}/addresses",
707+
[],
708+
{"project": "my-project", "region": "."},
709+
"Invalid value \\. for region\\.",
710+
],
711+
# Sub-template named matching * with . or ..
712+
[
704713
"/v2/{parent=projects/*/locations/*}/content:inspect",
705-
parent="projects/my-project/locations/..",
706-
)
707-
with pytest.raises(
708-
ValueError,
709-
match="Invalid value projects/my-project/locations/\\. for parent\\.",
710-
):
711-
path_template.expand(
714+
[],
715+
{"parent": "projects/my-project/locations/.."},
716+
"Invalid value projects/my-project/locations/\\.\\. for parent\\.",
717+
],
718+
[
712719
"/v2/{parent=projects/*/locations/*}/content:inspect",
713-
parent="projects/my-project/locations/.",
714-
)
715-
720+
[],
721+
{"parent": "projects/my-project/locations/."},
722+
"Invalid value projects/my-project/locations/\\. for parent\\.",
723+
],
724+
],
725+
)
726+
def test_path_traversal_dots_validation_star(tmpl, args, kwargs, expected_err_match):
727+
with pytest.raises(ValueError, match=expected_err_match):
728+
path_template.expand(tmpl, *args, **kwargs)
716729

717-
def test_path_traversal_dots_validation_double_star():
718-
# 2. Dots in values matched to **
719-
# Valid cases: leftover_segments > 0
720-
# leftover_segments == 1
721-
assert (
722-
path_template.expand(
723-
"/v3/{name=projects/*/monitoredResourceDescriptors/**}",
724-
name="projects/my-project/monitoredResourceDescriptors/instance/my-instance/..",
725-
)
726-
== "/v3/projects/my-project/monitoredResourceDescriptors/instance/my-instance/.."
727-
)
728730

731+
@pytest.mark.parametrize(
732+
"name_val, expected_path",
733+
[
734+
(
735+
"projects/my-project/monitoredResourceDescriptors/instance/my-instance/..",
736+
"/v3/projects/my-project/monitoredResourceDescriptors/instance/my-instance/..",
737+
),
738+
(
739+
"projects/my-project/monitoredResourceDescriptors/instance/my-instance/.",
740+
"/v3/projects/my-project/monitoredResourceDescriptors/instance/my-instance/.",
741+
),
742+
(
743+
"projects/my-project/monitoredResourceDescriptors/a/b/c/d/e/../../../..",
744+
"/v3/projects/my-project/monitoredResourceDescriptors/a/b/c/d/e/../../../..",
745+
),
746+
],
747+
)
748+
def test_path_traversal_dots_validation_double_star_valid(name_val, expected_path):
729749
assert (
730750
path_template.expand(
731751
"/v3/{name=projects/*/monitoredResourceDescriptors/**}",
732-
name="projects/my-project/monitoredResourceDescriptors/instance/my-instance/.",
752+
name=name_val,
733753
)
734-
== "/v3/projects/my-project/monitoredResourceDescriptors/instance/my-instance/."
754+
== expected_path
735755
)
736756

737-
assert (
738-
path_template.expand(
739-
"/v3/{name=projects/*/monitoredResourceDescriptors/**}",
740-
name="projects/my-project/monitoredResourceDescriptors/a/b/c/d/e/../../../..",
741-
)
742-
== "/v3/projects/my-project/monitoredResourceDescriptors/a/b/c/d/e/../../../.."
743-
)
744757

745-
# Invalid cases: resulting path traversal consumes whole value or overflows left
746-
invalid_cases = [
758+
@pytest.mark.parametrize(
759+
"name_val",
760+
[
747761
"projects/my-project/monitoredResourceDescriptors/.",
748762
"projects/my-project/monitoredResourceDescriptors/instance/my-instance/../..",
749763
"projects/my-project/monitoredResourceDescriptors/instance/../my-instance/..",
750764
"projects/my-project/monitoredResourceDescriptors/..",
751765
"projects/my-project/monitoredResourceDescriptors/instance/../..",
752766
"projects/my-project/monitoredResourceDescriptors/a/b/../../../c/d/e/..",
753-
]
754-
for case in invalid_cases:
755-
with pytest.raises(ValueError, match="Invalid value .* for name\\."):
756-
path_template.expand(
757-
"/v3/{name=projects/*/monitoredResourceDescriptors/**}", name=case
758-
)
759-
760-
761-
def test_percent_encoding_unreserved_characters():
762-
# 3. Values for all variable parts should be percent-encoded except for [-_.~/0-9a-zA-Z] characters.
763-
# We should keep [-_.~/0-9a-zA-Z] safe for both single-star and double-star
764-
result = path_template.expand("/v1/{name}", name="abc-._~")
765-
assert result == "/v1/abc-._~"
766-
767-
result = path_template.expand("/v1/{name=**}", name="abc-._~/")
768-
assert result == "/v1/abc-._~/"
769-
770-
# Other characters like '$', '?', '=', '#', ' ' should be encoded
771-
result = path_template.expand("/v1/{name}", name="a$b?c=d#e f")
772-
assert result == "/v1/a%24b%3Fc%3Dd%23e%20f"
767+
],
768+
)
769+
def test_path_traversal_dots_validation_double_star_invalid(name_val):
770+
with pytest.raises(ValueError, match="Invalid value .* for name\\."):
771+
path_template.expand(
772+
"/v3/{name=projects/*/monitoredResourceDescriptors/**}",
773+
name=name_val,
774+
)
775+
776+
777+
@pytest.mark.parametrize(
778+
"tmpl, kwargs, expected_result",
779+
[
780+
["/v1/{name}", {"name": "abc-._~"}, "/v1/abc-._~"],
781+
["/v1/{name=**}", {"name": "abc-._~/"}, "/v1/abc-._~/"],
782+
["/v1/{name}", {"name": "a/b"}, "/v1/a/b"],
783+
["/v1/{name}", {"name": "a$b?c=d#e f"}, "/v1/a%24b%3Fc%3Dd%23e%20f"],
784+
],
785+
)
786+
def test_percent_encoding_unreserved_characters(tmpl, kwargs, expected_result):
787+
result = path_template.expand(tmpl, **kwargs)
788+
assert result == expected_result
789+
# For single-segment with '/', validate should fail because '/' is preserved
790+
if "/" in kwargs.get("name", "") and tmpl == "/v1/{name}":
791+
assert not path_template.validate(tmpl, result)

0 commit comments

Comments
 (0)