openai/openai-python
Publicmirrored fromhttps://github.com/openai/openai-pythonAvailable
tests/test_utils/test_path.py
89lines · modecode
| 1 | from __future__ import annotations |
| 2 | |
| 3 | from typing import Any |
| 4 | |
| 5 | import pytest |
| 6 | |
| 7 | from openai._utils._path import path_template |
| 8 | |
| 9 | |
| 10 | @pytest.mark.parametrize( |
| 11 | "template, kwargs, expected", |
| 12 | [ |
| 13 | ("/v1/{id}", dict(id="abc"), "/v1/abc"), |
| 14 | ("/v1/{a}/{b}", dict(a="x", b="y"), "/v1/x/y"), |
| 15 | ("/v1/{a}{b}/path/{c}?val={d}#{e}", dict(a="x", b="y", c="z", d="u", e="v"), "/v1/xy/path/z?val=u#v"), |
| 16 | ("/{w}/{w}", dict(w="echo"), "/echo/echo"), |
| 17 | ("/v1/static", {}, "/v1/static"), |
| 18 | ("", {}, ""), |
| 19 | ("/v1/?q={n}&count=10", dict(n=42), "/v1/?q=42&count=10"), |
| 20 | ("/v1/{v}", dict(v=None), "/v1/null"), |
| 21 | ("/v1/{v}", dict(v=True), "/v1/true"), |
| 22 | ("/v1/{v}", dict(v=False), "/v1/false"), |
| 23 | ("/v1/{v}", dict(v=".hidden"), "/v1/.hidden"), # dot prefix ok |
| 24 | ("/v1/{v}", dict(v="file.txt"), "/v1/file.txt"), # dot in middle ok |
| 25 | ("/v1/{v}", dict(v="..."), "/v1/..."), # triple dot ok |
| 26 | ("/v1/{a}{b}", dict(a=".", b="txt"), "/v1/.txt"), # dot var combining with adjacent to be ok |
| 27 | ("/items?q={v}#{f}", dict(v=".", f=".."), "/items?q=.#.."), # dots in query/fragment are fine |
| 28 | ( |
| 29 | "/v1/{a}?query={b}", |
| 30 | dict(a="../../other/endpoint", b="a&bad=true"), |
| 31 | "/v1/..%2F..%2Fother%2Fendpoint?query=a%26bad%3Dtrue", |
| 32 | ), |
| 33 | ("/v1/{val}", dict(val="a/b/c"), "/v1/a%2Fb%2Fc"), |
| 34 | ("/v1/{val}", dict(val="a/b/c?query=value"), "/v1/a%2Fb%2Fc%3Fquery=value"), |
| 35 | ("/v1/{val}", dict(val="a/b/c?query=value&bad=true"), "/v1/a%2Fb%2Fc%3Fquery=value&bad=true"), |
| 36 | ("/v1/{val}", dict(val="%20"), "/v1/%2520"), # escapes escape sequences in input |
| 37 | # Query: slash and ? are safe, # is not |
| 38 | ("/items?q={v}", dict(v="a/b"), "/items?q=a/b"), |
| 39 | ("/items?q={v}", dict(v="a?b"), "/items?q=a?b"), |
| 40 | ("/items?q={v}", dict(v="a#b"), "/items?q=a%23b"), |
| 41 | ("/items?q={v}", dict(v="a b"), "/items?q=a%20b"), |
| 42 | # Fragment: slash and ? are safe |
| 43 | ("/docs#{v}", dict(v="a/b"), "/docs#a/b"), |
| 44 | ("/docs#{v}", dict(v="a?b"), "/docs#a?b"), |
| 45 | # Path: slash, ? and # are all encoded |
| 46 | ("/v1/{v}", dict(v="a/b"), "/v1/a%2Fb"), |
| 47 | ("/v1/{v}", dict(v="a?b"), "/v1/a%3Fb"), |
| 48 | ("/v1/{v}", dict(v="a#b"), "/v1/a%23b"), |
| 49 | # same var encoded differently by component |
| 50 | ( |
| 51 | "/v1/{v}?q={v}#{v}", |
| 52 | dict(v="a/b?c#d"), |
| 53 | "/v1/a%2Fb%3Fc%23d?q=a/b?c%23d#a/b?c%23d", |
| 54 | ), |
| 55 | ("/v1/{val}", dict(val="x?admin=true"), "/v1/x%3Fadmin=true"), # query injection |
| 56 | ("/v1/{val}", dict(val="x#admin"), "/v1/x%23admin"), # fragment injection |
| 57 | ], |
| 58 | ) |
| 59 | def test_interpolation(template: str, kwargs: dict[str, Any], expected: str) -> None: |
| 60 | assert path_template(template, **kwargs) == expected |
| 61 | |
| 62 | |
| 63 | def test_missing_kwarg_raises_key_error() -> None: |
| 64 | with pytest.raises(KeyError, match="org_id"): |
| 65 | path_template("/v1/{org_id}") |
| 66 | |
| 67 | |
| 68 | @pytest.mark.parametrize( |
| 69 | "template, kwargs", |
| 70 | [ |
| 71 | ("{a}/path", dict(a=".")), |
| 72 | ("{a}/path", dict(a="..")), |
| 73 | ("/v1/{a}", dict(a=".")), |
| 74 | ("/v1/{a}", dict(a="..")), |
| 75 | ("/v1/{a}/path", dict(a=".")), |
| 76 | ("/v1/{a}/path", dict(a="..")), |
| 77 | ("/v1/{a}{b}", dict(a=".", b=".")), # adjacent vars → ".." |
| 78 | ("/v1/{a}.", dict(a=".")), # var + static → ".." |
| 79 | ("/v1/{a}{b}", dict(a="", b=".")), # empty + dot → "." |
| 80 | ("/v1/%2e/{x}", dict(x="ok")), # encoded dot in static text |
| 81 | ("/v1/%2e./{x}", dict(x="ok")), # mixed encoded ".." in static |
| 82 | ("/v1/.%2E/{x}", dict(x="ok")), # mixed encoded ".." in static |
| 83 | ("/v1/{v}?q=1", dict(v="..")), |
| 84 | ("/v1/{v}#frag", dict(v="..")), |
| 85 | ], |
| 86 | ) |
| 87 | def test_dot_segment_rejected(template: str, kwargs: dict[str, Any]) -> None: |
| 88 | with pytest.raises(ValueError, match="dot-segment"): |
| 89 | path_template(template, **kwargs) |
| 90 | |