1# Copyright 2023 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15""
16
17load("@rules_testing//lib:test_suite.bzl", "test_suite")
18load("@rules_testing//lib:truth.bzl", "subjects")
19load("//python/private/pypi:parse_simpleapi_html.bzl", "parse_simpleapi_html")  # buildifier: disable=bzl-visibility
20
21_tests = []
22
23def _generate_html(*items):
24    return """\
25<html>
26  <head>
27    <meta name="pypi:repository-version" content="1.1">
28    <title>Links for foo</title>
29  </head>
30  <body>
31    <h1>Links for cengal</h1>
32{}
33</body>
34</html>
35""".format(
36        "\n".join([
37            "<a {}>{}</a><br />".format(
38                " ".join(item.attrs),
39                item.filename,
40            )
41            for item in items
42        ]),
43    )
44
45def _test_sdist(env):
46    # buildifier: disable=unsorted-dict-items
47    tests = [
48        (
49            struct(
50                attrs = [
51                    'href="https://example.org/full-url/foo-0.0.1.tar.gz#sha256=deadbeefasource"',
52                    'data-requires-python="&gt;=3.7"',
53                ],
54                filename = "foo-0.0.1.tar.gz",
55                url = "ignored",
56            ),
57            struct(
58                filename = "foo-0.0.1.tar.gz",
59                sha256 = "deadbeefasource",
60                url = "https://example.org/full-url/foo-0.0.1.tar.gz",
61                yanked = False,
62            ),
63        ),
64        (
65            struct(
66                attrs = [
67                    'href="https://example.org/full-url/foo-0.0.1.tar.gz#sha256=deadbeefasource"',
68                    'data-requires-python=">=3.7"',
69                ],
70                filename = "foo-0.0.1.tar.gz",
71                url = "ignored",
72            ),
73            struct(
74                filename = "foo-0.0.1.tar.gz",
75                sha256 = "deadbeefasource",
76                url = "https://example.org/full-url/foo-0.0.1.tar.gz",
77                yanked = False,
78            ),
79        ),
80    ]
81
82    for (input, want) in tests:
83        html = _generate_html(input)
84        got = parse_simpleapi_html(url = input.url, content = html)
85        env.expect.that_collection(got.sdists).has_size(1)
86        env.expect.that_collection(got.whls).has_size(0)
87        if not got:
88            fail("expected at least one element, but did not get anything from:\n{}".format(html))
89
90        actual = env.expect.that_struct(
91            got.sdists[want.sha256],
92            attrs = dict(
93                filename = subjects.str,
94                sha256 = subjects.str,
95                url = subjects.str,
96                yanked = subjects.bool,
97            ),
98        )
99        actual.filename().equals(want.filename)
100        actual.sha256().equals(want.sha256)
101        actual.url().equals(want.url)
102        actual.yanked().equals(want.yanked)
103
104_tests.append(_test_sdist)
105
106def _test_whls(env):
107    # buildifier: disable=unsorted-dict-items
108    tests = [
109        (
110            struct(
111                attrs = [
112                    'href="https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
113                    'data-requires-python="&gt;=3.7"',
114                    'data-dist-info-metadata="sha256=deadb00f"',
115                    'data-core-metadata="sha256=deadb00f"',
116                ],
117                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
118                url = "ignored",
119            ),
120            struct(
121                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
122                metadata_sha256 = "deadb00f",
123                metadata_url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata",
124                sha256 = "deadbeef",
125                url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
126                yanked = False,
127            ),
128        ),
129        (
130            struct(
131                attrs = [
132                    'href="https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
133                    'data-requires-python=">=3.7"',
134                    'data-dist-info-metadata="sha256=deadb00f"',
135                    'data-core-metadata="sha256=deadb00f"',
136                ],
137                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
138                url = "ignored",
139            ),
140            struct(
141                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
142                metadata_sha256 = "deadb00f",
143                metadata_url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata",
144                sha256 = "deadbeef",
145                url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
146                yanked = False,
147            ),
148        ),
149        (
150            struct(
151                attrs = [
152                    'href="https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
153                    'data-requires-python="&gt;=3.7"',
154                    'data-core-metadata="sha256=deadb00f"',
155                ],
156                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
157                url = "ignored",
158            ),
159            struct(
160                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
161                metadata_sha256 = "deadb00f",
162                metadata_url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata",
163                sha256 = "deadbeef",
164                url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
165                yanked = False,
166            ),
167        ),
168        (
169            struct(
170                attrs = [
171                    'href="https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
172                    'data-requires-python="&gt;=3.7"',
173                    'data-dist-info-metadata="sha256=deadb00f"',
174                ],
175                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
176                url = "ignored",
177            ),
178            struct(
179                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
180                metadata_sha256 = "deadb00f",
181                metadata_url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata",
182                sha256 = "deadbeef",
183                url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
184                yanked = False,
185            ),
186        ),
187        (
188            struct(
189                attrs = [
190                    'href="https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
191                    'data-requires-python="&gt;=3.7"',
192                ],
193                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
194                url = "ignored",
195            ),
196            struct(
197                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
198                metadata_sha256 = "",
199                metadata_url = "",
200                sha256 = "deadbeef",
201                url = "https://example.org/full-url/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
202                yanked = False,
203            ),
204        ),
205        (
206            struct(
207                attrs = [
208                    'href="../../foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl#sha256=deadbeef"',
209                    'data-requires-python="&gt;=3.7"',
210                    'data-dist-info-metadata="sha256=deadb00f"',
211                ],
212                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
213                url = "https://example.org/python-wheels/bar/foo/",
214            ),
215            struct(
216                filename = "foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
217                metadata_sha256 = "deadb00f",
218                metadata_url = "https://example.org/python-wheels/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata",
219                sha256 = "deadbeef",
220                url = "https://example.org/python-wheels/foo-0.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
221                yanked = False,
222            ),
223        ),
224        (
225            struct(
226                attrs = [
227                    'href="/whl/torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl#sha256=deadbeef"',
228                ],
229                filename = "torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
230                url = "https://download.pytorch.org/whl/cpu/torch",
231            ),
232            struct(
233                filename = "torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
234                metadata_sha256 = "",
235                metadata_url = "",
236                sha256 = "deadbeef",
237                url = "https://download.pytorch.org/whl/torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
238                yanked = False,
239            ),
240        ),
241        (
242            struct(
243                attrs = [
244                    'href="/whl/torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl#sha256=notdeadbeef"',
245                ],
246                filename = "torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
247                url = "http://download.pytorch.org/whl/cpu/torch",
248            ),
249            struct(
250                filename = "torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
251                metadata_sha256 = "",
252                metadata_url = "",
253                sha256 = "notdeadbeef",
254                url = "http://download.pytorch.org/whl/torch-2.0.0-cp38-cp38-manylinux2014_aarch64.whl",
255                yanked = False,
256            ),
257        ),
258        (
259            struct(
260                attrs = [
261                    'href="1.0.0/mypy_extensions-1.0.0-py3-none-any.whl#sha256=deadbeef"',
262                ],
263                filename = "mypy_extensions-1.0.0-py3-none-any.whl",
264                url = "https://example.org/simple/mypy_extensions",
265            ),
266            struct(
267                filename = "mypy_extensions-1.0.0-py3-none-any.whl",
268                metadata_sha256 = "",
269                metadata_url = "",
270                sha256 = "deadbeef",
271                url = "https://example.org/simple/mypy_extensions/1.0.0/mypy_extensions-1.0.0-py3-none-any.whl",
272                yanked = False,
273            ),
274        ),
275        (
276            struct(
277                attrs = [
278                    'href="unknown://example.com/mypy_extensions-1.0.0-py3-none-any.whl#sha256=deadbeef"',
279                ],
280                filename = "mypy_extensions-1.0.0-py3-none-any.whl",
281                url = "https://example.org/simple/mypy_extensions",
282            ),
283            struct(
284                filename = "mypy_extensions-1.0.0-py3-none-any.whl",
285                metadata_sha256 = "",
286                metadata_url = "",
287                sha256 = "deadbeef",
288                url = "https://example.org/simple/mypy_extensions/unknown://example.com/mypy_extensions-1.0.0-py3-none-any.whl",
289                yanked = False,
290            ),
291        ),
292    ]
293
294    for (input, want) in tests:
295        html = _generate_html(input)
296        got = parse_simpleapi_html(url = input.url, content = html)
297        env.expect.that_collection(got.sdists).has_size(0)
298        env.expect.that_collection(got.whls).has_size(1)
299        if not got:
300            fail("expected at least one element, but did not get anything from:\n{}".format(html))
301
302        actual = env.expect.that_struct(
303            got.whls[want.sha256],
304            attrs = dict(
305                filename = subjects.str,
306                metadata_sha256 = subjects.str,
307                metadata_url = subjects.str,
308                sha256 = subjects.str,
309                url = subjects.str,
310                yanked = subjects.bool,
311            ),
312        )
313        actual.filename().equals(want.filename)
314        actual.metadata_sha256().equals(want.metadata_sha256)
315        actual.metadata_url().equals(want.metadata_url)
316        actual.sha256().equals(want.sha256)
317        actual.url().equals(want.url)
318        actual.yanked().equals(want.yanked)
319
320_tests.append(_test_whls)
321
322def parse_simpleapi_html_test_suite(name):
323    """Create the test suite.
324
325    Args:
326        name: the name of the test suite
327    """
328    test_suite(name = name, basic_tests = _tests)
329