test_rpc.py 25.2 KB
Newer Older
1
2
import re

Kevin Morris's avatar
Kevin Morris committed
3
from http import HTTPStatus
4
from typing import List
Kevin Morris's avatar
Kevin Morris committed
5
6
from unittest import mock

7
import orjson
8
import pytest
9
10

from fastapi.testclient import TestClient
Kevin Morris's avatar
Kevin Morris committed
11
from redis.client import Pipeline
12

13
14
15
import aurweb.models.dependency_type as dt
import aurweb.models.relation_type as rt

16
from aurweb import asgi, config, db, rpc, scripts, time
17
from aurweb.models.account_type import USER_ID
18
19
20
21
22
23
24
25
26
from aurweb.models.license import License
from aurweb.models.package import Package
from aurweb.models.package_base import PackageBase
from aurweb.models.package_dependency import PackageDependency
from aurweb.models.package_keyword import PackageKeyword
from aurweb.models.package_license import PackageLicense
from aurweb.models.package_relation import PackageRelation
from aurweb.models.package_vote import PackageVote
from aurweb.models.user import User
Kevin Morris's avatar
Kevin Morris committed
27
from aurweb.redis import redis_connection
28
29


30
31
32
@pytest.fixture
def client() -> TestClient:
    yield TestClient(app=asgi.app)
33
34


35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@pytest.fixture
def user(db_test) -> User:
    with db.begin():
        user = db.create(User, Username="test", Email="test@example.org",
                         RealName="Test User 1", Passwd=str(),
                         AccountTypeID=USER_ID)
    yield user


@pytest.fixture
def user2() -> User:
    with db.begin():
        user = db.create(User, Username="user2", Email="user2@example.org",
                         RealName="Test User 2", Passwd=str(),
                         AccountTypeID=USER_ID)
    yield user


@pytest.fixture
def user3() -> User:
    with db.begin():
        user = db.create(User, Username="user3", Email="user3@example.org",
                         RealName="Test User 3", Passwd=str(),
                         AccountTypeID=USER_ID)
    yield user


@pytest.fixture
def packages(user: User, user2: User, user3: User) -> List[Package]:
    output = []

    # Create package records used in our tests.
    with db.begin():
        pkgbase = db.create(PackageBase, Name="big-chungus",
                            Maintainer=user, Packager=user)
        pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name,
                        Description="Bunny bunny around bunny",
                        URL="https://example.com/")
        output.append(pkg)

        pkgbase = db.create(PackageBase, Name="chungy-chungus",
                            Maintainer=user, Packager=user)
        pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name,
                        Description="Wubby wubby on wobba wuubu",
                        URL="https://example.com/")
        output.append(pkg)

        pkgbase = db.create(PackageBase, Name="gluggly-chungus",
                            Maintainer=user, Packager=user)
        pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name,
                        Description="glurrba glurrba gur globba",
                        URL="https://example.com/")
        output.append(pkg)

        pkgbase = db.create(PackageBase, Name="fugly-chungus",
                            Maintainer=user, Packager=user)
91
92

        desc = "A Package belonging to a PackageBase with another name."
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
        pkg = db.create(Package, PackageBase=pkgbase, Name="other-pkg",
                        Description=desc, URL="https://example.com")
        output.append(pkg)

        pkgbase = db.create(PackageBase, Name="woogly-chungus")
        pkg = db.create(Package, PackageBase=pkgbase, Name=pkgbase.Name,
                        Description="wuggla woblabeloop shemashmoop",
                        URL="https://example.com/")
        output.append(pkg)

    # Setup a few more related records on the first package:
    # a license, some keywords and some votes.
    with db.begin():
        lic = db.create(License, Name="GPL")
        db.create(PackageLicense, Package=output[0], License=lic)

        for keyword in ["big-chungus", "smol-chungus", "sizeable-chungus"]:
            db.create(PackageKeyword,
                      PackageBase=output[0].PackageBase,
                      Keyword=keyword)

114
        now = time.utcnow()
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
        for user_ in [user, user2, user3]:
            db.create(PackageVote, User=user_,
                      PackageBase=output[0].PackageBase, VoteTS=now)
    scripts.popupdate.run_single(output[0].PackageBase)

    yield output


@pytest.fixture
def depends(packages: List[Package]) -> List[PackageDependency]:
    output = []

    with db.begin():
        dep = db.create(PackageDependency,
                        Package=packages[0],
                        DepTypeID=dt.DEPENDS_ID,
                        DepName="chungus-depends")
        output.append(dep)

        dep = db.create(PackageDependency,
                        Package=packages[1],
                        DepTypeID=dt.DEPENDS_ID,
                        DepName="chungy-depends")
        output.append(dep)

        dep = db.create(PackageDependency,
                        Package=packages[0],
                        DepTypeID=dt.OPTDEPENDS_ID,
                        DepName="chungus-optdepends",
                        DepCondition="=50")
        output.append(dep)

        dep = db.create(PackageDependency,
                        Package=packages[0],
                        DepTypeID=dt.MAKEDEPENDS_ID,
                        DepName="chungus-makedepends")
        output.append(dep)

        dep = db.create(PackageDependency,
                        Package=packages[0],
                        DepTypeID=dt.CHECKDEPENDS_ID,
                        DepName="chungus-checkdepends")
        output.append(dep)

    yield output


@pytest.fixture
def relations(user: User, packages: List[Package]) -> List[PackageRelation]:
    output = []

    with db.begin():
        rel = db.create(PackageRelation,
                        Package=packages[0],
                        RelTypeID=rt.CONFLICTS_ID,
                        RelName="chungus-conflicts")
        output.append(rel)

        rel = db.create(PackageRelation,
                        Package=packages[1],
                        RelTypeID=rt.CONFLICTS_ID,
                        RelName="chungy-conflicts")
        output.append(rel)

        rel = db.create(PackageRelation,
                        Package=packages[0],
                        RelTypeID=rt.PROVIDES_ID,
                        RelName="chungus-provides",
                        RelCondition="<=200")
        output.append(rel)

        rel = db.create(PackageRelation,
                        Package=packages[0],
                        RelTypeID=rt.REPLACES_ID,
                        RelName="chungus-replaces",
                        RelCondition="<=200")
        output.append(rel)

    # Finally, yield the packages.
    yield output


@pytest.fixture(autouse=True)
def setup(db_test):
    # Create some extra package relationships.
    pass
201

202

Kevin Morris's avatar
Kevin Morris committed
203
204
205
206
207
@pytest.fixture
def pipeline():
    redis = redis_connection()
    pipeline = redis.pipeline()

208
209
    # The 'testclient' host is used when requesting the app
    # via fastapi.testclient.TestClient.
Kevin Morris's avatar
Kevin Morris committed
210
211
    pipeline.delete("ratelimit-ws:testclient")
    pipeline.delete("ratelimit:testclient")
212
    pipeline.execute()
Kevin Morris's avatar
Kevin Morris committed
213
214
215
216

    yield pipeline


217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
def test_rpc_documentation(client: TestClient):
    with client as request:
        resp = request.get("/rpc")
    assert resp.status_code == int(HTTPStatus.OK)
    assert "aurweb RPC Interface" in resp.text


def test_rpc_documentation_missing():
    config_get = config.get

    def mock_get(section: str, key: str) -> str:
        if section == "options" and key == "aurwebdir":
            return "/missing"
        return config_get(section, key)

    with mock.patch("aurweb.config.get", side_effect=mock_get):
        config.rehash()
        expr = r"^doc/rpc\.html could not be read$"
        with pytest.raises(OSError, match=expr):
            rpc.documentation()
    config.rehash()


240
241
242
243
244
def test_rpc_singular_info(client: TestClient,
                           user: User,
                           packages: List[Package],
                           depends: List[PackageDependency],
                           relations: List[PackageRelation]):
245
    # Define expected response.
246
    pkg = packages[0]
247
    expected_data = {
248
249
        "version": 5,
        "results": [{
250
251
252
253
254
255
256
            "Name": pkg.Name,
            "Version": pkg.Version,
            "Description": pkg.Description,
            "URL": pkg.URL,
            "PackageBase": pkg.PackageBase.Name,
            "NumVotes": pkg.PackageBase.NumVotes,
            "Popularity": float(pkg.PackageBase.Popularity),
257
            "OutOfDate": None,
258
259
            "Maintainer": user.Username,
            "URLPath": f"/cgit/aur.git/snapshot/{pkg.Name}.tar.gz",
260
261
262
263
264
265
266
            "Depends": ["chungus-depends"],
            "OptDepends": ["chungus-optdepends=50"],
            "MakeDepends": ["chungus-makedepends"],
            "CheckDepends": ["chungus-checkdepends"],
            "Conflicts": ["chungus-conflicts"],
            "Provides": ["chungus-provides<=200"],
            "Replaces": ["chungus-replaces<=200"],
267
            "License": [pkg.package_licenses.first().License.Name],
268
269
270
271
272
273
274
275
276
            "Keywords": [
                "big-chungus",
                "sizeable-chungus",
                "smol-chungus"
            ]
        }],
        "resultcount": 1,
        "type": "multiinfo"
    }
277
278

    # Make dummy request.
279
    with client as request:
280
281
282
283
284
        resp = request.get("/rpc", params={
            "v": 5,
            "type": "info",
            "arg": ["chungy-chungus", "big-chungus"],
        })
285
286

    # Load  request response into Python dictionary.
287
    response_data = orjson.loads(resp.text)
288
289
290
291
292

    # Remove the FirstSubmitted LastModified, ID and PackageBaseID keys from
    # reponse, as the key's values aren't guaranteed to match between the two
    # (the keys are already removed from 'expected_data').
    for i in ["FirstSubmitted", "LastModified", "ID", "PackageBaseID"]:
293
        response_data["results"][0].pop(i)
294
295

    # Validate that the new dictionaries are the same.
296
    assert response_data == expected_data
297
298


299
def test_rpc_nonexistent_package(client: TestClient):
300
    # Make dummy request.
301
302
    with client as request:
        response = request.get("/rpc/?v=5&type=info&arg=nonexistent-package")
303
304
305
306
307
308
309
310

    # Load request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert response_data["resultcount"] == 0


311
def test_rpc_multiinfo(client: TestClient, packages: List[Package]):
312
313
    # Make dummy request.
    request_packages = ["big-chungus", "chungy-chungus"]
314
    with client as request:
315
316
317
        response = request.get("/rpc", params={
            "v": 5, "type": "info", "arg[]": request_packages
        })
318
319
320
321
322
323
324
325
326
327
328

    # Load request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    for i in response_data["results"]:
        request_packages.remove(i["Name"])

    assert request_packages == []


329
def test_rpc_mixedargs(client: TestClient, packages: List[Package]):
330
331
332
333
    # Make dummy request.
    response1_packages = ["gluggly-chungus"]
    response2_packages = ["gluggly-chungus", "chungy-chungus"]

334
    with client as request:
335
        # Supply all of the args in the url to enforce ordering.
336
337
338
339
340
341
        response1 = request.get(
            "/rpc?v=5&arg[]=big-chungus&arg=gluggly-chungus&type=info")
    assert response1.status_code == int(HTTPStatus.OK)

    with client as request:
        response2 = request.get(
342
343
            "/rpc?v=5&arg=big-chungus&arg[]=gluggly-chungus"
            "&type=info&arg[]=chungy-chungus")
344
    assert response1.status_code == int(HTTPStatus.OK)
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360

    # Load request response into Python dictionary.
    response1_data = orjson.loads(response1.content.decode())
    response2_data = orjson.loads(response2.content.decode())

    # Validate data.
    for i in response1_data["results"]:
        response1_packages.remove(i["Name"])

    for i in response2_data["results"]:
        response2_packages.remove(i["Name"])

    for i in [response1_packages, response2_packages]:
        assert i == []


361
362
363
364
365
366
367
368
369
def test_rpc_no_dependencies_omits_key(client: TestClient, user: User,
                                       packages: List[Package],
                                       depends: List[PackageDependency],
                                       relations: List[PackageRelation]):
    """
    This makes sure things like 'MakeDepends' get removed from JSON strings
    when they don't have set values.
    """
    pkg = packages[1]
370
    expected_response = {
371
372
        'version': 5,
        'results': [{
373
374
375
376
377
378
379
            'Name': pkg.Name,
            'Version': pkg.Version,
            'Description': pkg.Description,
            'URL': pkg.URL,
            'PackageBase': pkg.PackageBase.Name,
            'NumVotes': pkg.PackageBase.NumVotes,
            'Popularity': int(pkg.PackageBase.Popularity),
380
            'OutOfDate': None,
381
            'Maintainer': user.Username,
382
383
384
385
386
387
388
389
390
            'URLPath': '/cgit/aur.git/snapshot/chungy-chungus.tar.gz',
            'Depends': ['chungy-depends'],
            'Conflicts': ['chungy-conflicts'],
            'License': [],
            'Keywords': []
        }],
        'resultcount': 1,
        'type': 'multiinfo'
    }
391
392

    # Make dummy request.
393
    with client as request:
394
395
396
        response = request.get("/rpc", params={
            "v": 5, "type": "info", "arg": "chungy-chungus"
        })
397
398
399
400
401
402
403
404
405
    response_data = orjson.loads(response.content.decode())

    # Remove inconsistent keys.
    for i in ["ID", "PackageBaseID", "FirstSubmitted", "LastModified"]:
        response_data["results"][0].pop(i)

    assert response_data == expected_response


406
def test_rpc_bad_type(client: TestClient):
407
408
    # Define expected response.
    expected_data = {
409
410
411
412
413
414
        'version': 5,
        'results': [],
        'resultcount': 0,
        'type': 'error',
        'error': 'Incorrect request type specified.'
    }
415
416

    # Make dummy request.
417
    with client as request:
418
419
420
        response = request.get("/rpc", params={
            "v": 5, "type": "invalid-type", "arg": "big-chungus"
        })
421
422
423
424
425
426
427
428

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert expected_data == response_data


429
def test_rpc_bad_version(client: TestClient):
430
    # Define expected response.
431
432
433
434
435
436
437
    expected_data = {
        'version': 0,
        'resultcount': 0,
        'results': [],
        'type': 'error',
        'error': 'Invalid version specified.'
    }
438
439

    # Make dummy request.
440
    with client as request:
441
442
443
        response = request.get("/rpc", params={
            "v": 0, "type": "info", "arg": "big-chungus"
        })
444
445
446
447
448
449
450
451

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert expected_data == response_data


452
def test_rpc_no_version(client: TestClient):
453
    # Define expected response.
454
455
456
457
458
459
460
    expected_data = {
        'version': None,
        'resultcount': 0,
        'results': [],
        'type': 'error',
        'error': 'Please specify an API version.'
    }
461
462

    # Make dummy request.
463
    with client as request:
464
465
466
467
        response = request.get("/rpc", params={
            "type": "info",
            "arg": "big-chungus"
        })
468
469
470
471
472
473
474
475

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert expected_data == response_data


476
def test_rpc_no_type(client: TestClient):
477
478
    # Define expected response.
    expected_data = {
479
480
481
482
483
484
        'version': 5,
        'results': [],
        'resultcount': 0,
        'type': 'error',
        'error': 'No request type/data specified.'
    }
485
486

    # Make dummy request.
487
    with client as request:
488
        response = request.get("/rpc", params={"v": 5, "arg": "big-chungus"})
489
490
491
492
493
494
495
496

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert expected_data == response_data


497
def test_rpc_no_args(client: TestClient):
498
499
    # Define expected response.
    expected_data = {
500
501
502
503
504
505
        'version': 5,
        'results': [],
        'resultcount': 0,
        'type': 'error',
        'error': 'No request type/data specified.'
    }
506
507

    # Make dummy request.
508
    with client as request:
509
        response = request.get("/rpc", params={"v": 5, "type": "info"})
510
511
512
513
514
515
516
517

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert expected_data == response_data


518
def test_rpc_no_maintainer(client: TestClient, packages: List[Package]):
519
    # Make dummy request.
520
    with client as request:
521
522
523
        response = request.get("/rpc", params={
            "v": 5, "type": "info", "arg": "woogly-chungus"
        })
524
525
526
527
528
529

    # Load  request response into Python dictionary.
    response_data = orjson.loads(response.content.decode())

    # Validate data.
    assert response_data["results"][0]["Maintainer"] is None
530
531


532
533
def test_rpc_suggest_pkgbase(client: TestClient, packages: List[Package]):
    params = {"v": 5, "type": "suggest-pkgbase", "arg": "big"}
534
    with client as request:
535
        response = request.get("/rpc", params=params)
536
537
538
    data = response.json()
    assert data == ["big-chungus"]

539
    params["arg"] = "chungy"
540
    with client as request:
541
        response = request.get("/rpc", params=params)
542
543
    data = response.json()
    assert data == ["chungy-chungus"]
544

545
    # Test no arg supplied.
546
    del params["arg"]
547
    with client as request:
548
        response = request.get("/rpc", params=params)
549
550
551
    data = response.json()
    assert data == []

552

553
554
def test_rpc_suggest(client: TestClient, packages: List[Package]):
    params = {"v": 5, "type": "suggest", "arg": "other"}
555
    with client as request:
556
        response = request.get("/rpc", params=params)
557
558
559
560
    data = response.json()
    assert data == ["other-pkg"]

    # Test non-existent Package.
561
    params["arg"] = "nonexistent"
562
    with client as request:
563
        response = request.get("/rpc", params=params)
564
565
566
    data = response.json()
    assert data == []

567
    # Test no arg supplied.
568
    del params["arg"]
569
    with client as request:
570
        response = request.get("/rpc", params=params)
571
572
573
    data = response.json()
    assert data == []

574

Kevin Morris's avatar
Kevin Morris committed
575
576
577
578
579
580
581
582
583
def mock_config_getint(section: str, key: str):
    if key == "request_limit":
        return 4
    elif key == "window_length":
        return 100
    return config.getint(section, key)


@mock.patch("aurweb.config.getint", side_effect=mock_config_getint)
584
def test_rpc_ratelimit(getint: mock.MagicMock, client: TestClient,
585
586
587
                       pipeline: Pipeline, packages: List[Package]):
    params = {"v": 5, "type": "suggest-pkgbase", "arg": "big"}

Kevin Morris's avatar
Kevin Morris committed
588
589
    for i in range(4):
        # The first 4 requests should be good.
590
        with client as request:
591
            response = request.get("/rpc", params=params)
Kevin Morris's avatar
Kevin Morris committed
592
593
594
        assert response.status_code == int(HTTPStatus.OK)

    # The fifth request should be banned.
595
    with client as request:
596
        response = request.get("/rpc", params=params)
Kevin Morris's avatar
Kevin Morris committed
597
598
599
600
601
602
603
604
605
    assert response.status_code == int(HTTPStatus.TOO_MANY_REQUESTS)

    # Delete the cached records.
    pipeline.delete("ratelimit-ws:testclient")
    pipeline.delete("ratelimit:testclient")
    one, two = pipeline.execute()
    assert one and two

    # The new first request should be good.
606
    with client as request:
607
        response = request.get("/rpc", params=params)
Kevin Morris's avatar
Kevin Morris committed
608
    assert response.status_code == int(HTTPStatus.OK)
609
610


611
612
def test_rpc_etag(client: TestClient, packages: List[Package]):
    params = {"v": 5, "type": "suggest-pkgbase", "arg": "big"}
613
614

    with client as request:
615
616
617
618
        response1 = request.get("/rpc", params=params)
    with client as request:
        response2 = request.get("/rpc", params=params)

619
620
621
    assert response1.headers.get("ETag") is not None
    assert response1.headers.get("ETag") != str()
    assert response1.headers.get("ETag") == response2.headers.get("ETag")
622
623


624
def test_rpc_search_arg_too_small(client: TestClient):
625
    params = {"v": 5, "type": "search", "arg": "b"}
626
    with client as request:
627
        response = request.get("/rpc", params=params)
628
629
630
631
    assert response.status_code == int(HTTPStatus.OK)
    assert response.json().get("error") == "Query arg too small."


632
633
def test_rpc_search(client: TestClient, packages: List[Package]):
    params = {"v": 5, "type": "search", "arg": "big"}
634
    with client as request:
635
        response = request.get("/rpc", params=params)
636
637
638
639
640
641
    assert response.status_code == int(HTTPStatus.OK)

    data = response.json()
    assert data.get("resultcount") == 1

    result = data.get("results")[0]
642
    assert result.get("Name") == packages[0].Name
643

644
645
646
    # Test the If-None-Match headers.
    etag = response.headers.get("ETag").strip('"')
    headers = {"If-None-Match": etag}
647
    response = request.get("/rpc", params=params, headers=headers)
648
649
650
    assert response.status_code == int(HTTPStatus.NOT_MODIFIED)
    assert response.content == b''

651
    # No args on non-m by types return an error.
652
653
654
    del params["arg"]
    with client as request:
        response = request.get("/rpc", params=params)
655
656
657
    assert response.json().get("error") == "No request type/data specified."


658
659
def test_rpc_msearch(client: TestClient, user: User, packages: List[Package]):
    params = {"v": 5, "type": "msearch", "arg": user.Username}
660
    with client as request:
661
        response = request.get("/rpc", params=params)
662
663
664
665
666
    data = response.json()

    # user1 maintains 4 packages; assert that we got them all.
    assert data.get("resultcount") == 4
    names = list(sorted(r.get("Name") for r in data.get("results")))
667
    expected_results = [
668
669
670
671
        "big-chungus",
        "chungy-chungus",
        "gluggly-chungus",
        "other-pkg"
672
    ]
673
674
675
    assert names == expected_results

    # Search for a non-existent maintainer, giving us zero packages.
676
677
    params["arg"] = "blah-blah"
    response = request.get("/rpc", params=params)
678
679
680
    data = response.json()
    assert data.get("resultcount") == 0

681
682
683
    with db.begin():
        packages[0].PackageBase.Maintainer = None

684
685
    # A missing arg still succeeds, but it returns all orphans.
    # Just verify that we receive no error and the orphaned result.
686
687
    params.pop("arg")
    response = request.get("/rpc", params=params)
688
689
690
    data = response.json()
    assert data.get("resultcount") == 1
    result = data.get("results")[0]
691
    assert result.get("Name") == "big-chungus"
692
693


694
695
696
697
698
def test_rpc_search_depends(client: TestClient, packages: List[Package],
                            depends: List[PackageDependency]):
    params = {
        "v": 5, "type": "search", "by": "depends", "arg": "chungus-depends"
    }
699
    with client as request:
700
        response = request.get("/rpc", params=params)
701
702
703
    data = response.json()
    assert data.get("resultcount") == 1
    result = data.get("results")[0]
704
    assert result.get("Name") == packages[0].Name
705
706


707
708
709
710
711
712
713
714
def test_rpc_search_makedepends(client: TestClient, packages: List[Package],
                                depends: List[PackageDependency]):
    params = {
        "v": 5,
        "type": "search",
        "by": "makedepends",
        "arg": "chungus-makedepends"
    }
715
    with client as request:
716
        response = request.get("/rpc", params=params)
717
718
719
    data = response.json()
    assert data.get("resultcount") == 1
    result = data.get("results")[0]
720
    assert result.get("Name") == packages[0].Name
721
722


723
724
725
726
727
728
729
730
def test_rpc_search_optdepends(client: TestClient, packages: List[Package],
                               depends: List[PackageDependency]):
    params = {
        "v": 5,
        "type": "search",
        "by": "optdepends",
        "arg": "chungus-optdepends"
    }
731
    with client as request:
732
        response = request.get("/rpc", params=params)
733
734
735
    data = response.json()
    assert data.get("resultcount") == 1
    result = data.get("results")[0]
736
    assert result.get("Name") == packages[0].Name
737
738


739
740
741
742
743
744
745
746
def test_rpc_search_checkdepends(client: TestClient, packages: List[Package],
                                 depends: List[PackageDependency]):
    params = {
        "v": 5,
        "type": "search",
        "by": "checkdepends",
        "arg": "chungus-checkdepends"
    }
747
    with client as request:
748
        response = request.get("/rpc", params=params)
749
750
751
    data = response.json()
    assert data.get("resultcount") == 1
    result = data.get("results")[0]
752
    assert result.get("Name") == packages[0].Name
753
754


755
def test_rpc_incorrect_by(client: TestClient):
756
    params = {"v": 5, "type": "search", "by": "fake", "arg": "big"}
757
    with client as request:
758
        response = request.get("/rpc", params=params)
759
    assert response.json().get("error") == "Incorrect by field specified."
760
761


762
def test_rpc_jsonp_callback(client: TestClient):
763
764
765
766
767
    """ Test the callback parameter.

    For end-to-end verification, the `examples/jsonp.html` file can be
    used to submit jsonp callback requests to the RPC.
    """
768
769
770
771
772
773
    params = {
        "v": 5,
        "type": "search",
        "arg": "big",
        "callback": "jsonCallback"
    }
774
    with client as request:
775
        response = request.get("/rpc", params=params)
776
777
    assert response.headers.get("content-type") == "text/javascript"
    assert re.search(r'^/\*\*/jsonCallback\(.*\)$', response.text) is not None
778
779

    # Test an invalid callback name; we get an application/json error.
780
    params["callback"] = "jsonCallback!"
781
    with client as request:
782
        response = request.get("/rpc", params=params)
783
784
    assert response.headers.get("content-type") == "application/json"
    assert response.json().get("error") == "Invalid callback name."