Skip to content

Commit af54d23

Browse files
authored
Merge pull request #59 from GitGuardian/agateau/improve-tests
Improve tests
2 parents d0188a1 + 1bda1b4 commit af54d23

File tree

4 files changed

+120
-109
lines changed

4 files changed

+120
-109
lines changed

.github/workflows/test-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ jobs:
6363
6464
- name: Test with pytest
6565
run: |
66-
pipenv run coverage run --source pygitguardian -m pytest --disable-pytest-warnings
66+
pipenv run coverage run --source pygitguardian -m pytest
6767
pipenv run coverage report --fail-under=80
6868
pipenv run coverage xml
6969
env:

Pipfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ urllib3 = "<2" # pin until https://github.com/kevin1024/vcrpy/issues/688 is fixe
1919
mypy = "==0.961"
2020
types-requests = "*"
2121
scriv = { version = "*", extras = ["toml"] }
22+
responses = ">=0.23.1,<0.24.0"

pygitguardian/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,7 +505,7 @@ class HoneytokenResponseSchema(BaseSchema):
505505
revoker_id = fields.Int(allow_none=True)
506506
creator_api_token_id = fields.String(allow_none=True)
507507
revoker_api_token_id = fields.String(allow_none=True)
508-
token = fields.Mapping(key=fields.String(), value=fields.String())
508+
token = fields.Mapping(fields.String(), fields.String())
509509
tags = fields.List(fields.String())
510510

511511
@post_load

tests/test_client.py

Lines changed: 117 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
from collections import OrderedDict
44
from datetime import date
55
from typing import Any, Dict, List, Optional, Type
6-
from unittest.mock import Mock, patch
6+
from unittest.mock import patch
77

88
import pytest
9+
import responses
910
from marshmallow import ValidationError
10-
from requests.models import Response
11+
from responses import matchers
1112

1213
from pygitguardian import GGClient
1314
from pygitguardian.client import is_ok, load_detail
@@ -518,10 +519,8 @@ def test_assert_content_type():
518519
),
519520
],
520521
)
521-
@patch("requests.Session.request")
522-
@my_vcr.use_cassette
522+
@responses.activate
523523
def test_extra_headers(
524-
request_mock: Mock,
525524
client: GGClient,
526525
session_headers: Any,
527526
extra_headers: Optional[Dict[str, str]],
@@ -534,70 +533,72 @@ def test_extra_headers(
534533
"""
535534
client.session.headers = session_headers
536535

537-
mock_response = Mock(spec=Response)
538-
mock_response.headers = {"content-type": "text"}
539-
mock_response.text = "some error"
540-
mock_response.status_code = 400
541-
request_mock.return_value = mock_response
536+
mock_response = responses.post(
537+
url=client._url_from_endpoint("multiscan", "v1"),
538+
content_type="text/plain",
539+
body="some error",
540+
status=400,
541+
match=[matchers.header_matcher(extra_headers)] if extra_headers else [],
542+
)
542543

543544
client.multi_content_scan(
544545
[{"filename": FILENAME, "document": DOCUMENT}],
545546
extra_headers=extra_headers,
546547
)
547-
assert request_mock.called
548-
_, kwargs = request_mock.call_args
549-
assert expected_headers == kwargs["headers"]
550-
548+
assert mock_response.call_count == 1
549+
550+
# Same test for content_scan
551+
mock_response = responses.post(
552+
url=client._url_from_endpoint("scan", "v1"),
553+
content_type="text/plain",
554+
body="some error",
555+
status=400,
556+
match=[matchers.header_matcher(extra_headers)] if extra_headers else [],
557+
)
551558
client.content_scan("some_string", extra_headers=extra_headers)
552-
assert request_mock.called
553-
_, kwargs = request_mock.call_args
554-
assert expected_headers == kwargs["headers"]
559+
assert mock_response.call_count == 1
555560

556561

557-
@patch("requests.Session.request")
562+
@responses.activate
558563
def test_multiscan_parameters(
559-
request_mock: Mock,
560564
client: GGClient,
561565
):
562566
"""
563567
GIVEN a ggclient
564568
WHEN calling multi_content_scan with parameters
565569
THEN the parameters are passed in the request
566570
"""
567-
mock_response = Mock(spec=Response)
568-
mock_response.headers = {"content-type": "application/json"}
569-
mock_response.status_code = 200
570-
mock_response.json.return_value = [
571-
{
572-
"policy_break_count": 1,
573-
"policies": ["pol"],
574-
"policy_breaks": [
575-
{
576-
"type": "break",
577-
"policy": "mypol",
578-
"matches": [
579-
{
580-
"match": "hello",
581-
"type": "hello",
582-
}
583-
],
584-
}
585-
],
586-
}
587-
]
588571

589-
request_mock.return_value = mock_response
590-
591-
params = {"ignore_known_secrets": True}
572+
mock_response = responses.post(
573+
url=client._url_from_endpoint("multiscan", "v1"),
574+
status=200,
575+
match=[matchers.query_param_matcher({"ignore_known_secrets": True})],
576+
json=[
577+
{
578+
"policy_break_count": 1,
579+
"policies": ["pol"],
580+
"policy_breaks": [
581+
{
582+
"type": "break",
583+
"policy": "mypol",
584+
"matches": [
585+
{
586+
"match": "hello",
587+
"type": "hello",
588+
}
589+
],
590+
}
591+
],
592+
}
593+
],
594+
)
592595

593596
client.multi_content_scan(
594597
[{"filename": FILENAME, "document": DOCUMENT}],
595598
ignore_known_secrets=True,
596599
)
597600

598-
assert request_mock.called
599-
# 1 is for kwargs
600-
assert request_mock.call_args[1]["params"] == params
601+
assert mock_response.call_count == 1
601602

602603

603604
def test_quota_overview(client: GGClient):
@@ -620,109 +621,118 @@ def test_quota_overview(client: GGClient):
620621
assert type(json.loads(quota_response_json)) == dict
621622

622623

623-
@pytest.mark.parametrize("method", ["get", "post"])
624-
@patch("requests.Session.request")
625-
def test_versions_from_headers(request_mock: Mock, client: GGClient, method):
624+
@pytest.mark.parametrize("method", ["GET", "POST"])
625+
@responses.activate
626+
def test_versions_from_headers(client: GGClient, method):
627+
"""
628+
GIVEN a GGClient instance
629+
WHEN an HTTP request to GitGuardian API is made
630+
THEN the app_version and secrets_engine_version fields are set from the headers of
631+
the HTTP response
632+
"""
633+
url = client._url_from_endpoint("endpoint", "v1")
626634
app_version_value = "1.0"
627635
secrets_engine_version_value = "2.0"
628636

629-
mock_response = Mock(spec=Response)
630-
mock_response.headers = {
631-
"X-App-Version": app_version_value,
632-
"X-Secrets-Engine-Version": secrets_engine_version_value,
633-
}
634-
request_mock.return_value = mock_response
637+
mock_response = responses.add(
638+
method=method,
639+
url=url,
640+
headers={
641+
"X-App-Version": app_version_value,
642+
"X-Secrets-Engine-Version": secrets_engine_version_value,
643+
},
644+
)
635645

636646
client.request(method=method, endpoint="endpoint")
637-
assert request_mock.called
638-
639-
assert client.app_version is app_version_value
640-
assert client.secrets_engine_version is secrets_engine_version_value
647+
assert mock_response.call_count == 1
641648

642-
mock_response = Mock(spec=Response)
643-
mock_response.headers = {}
644-
request_mock.return_value = mock_response
649+
assert client.app_version == app_version_value
650+
assert client.secrets_engine_version == secrets_engine_version_value
645651

652+
# WHEN making another HTTP call whose response headers does not contain the version
653+
# fields
654+
# THEN known version fields remain set
655+
mock_response = responses.add(method=method, url=url)
646656
client.request(method=method, endpoint="endpoint")
647-
assert request_mock.called
657+
assert mock_response.call_count == 1
648658

649-
assert client.app_version is app_version_value
650-
assert client.secrets_engine_version is secrets_engine_version_value
659+
assert client.app_version == app_version_value
660+
assert client.secrets_engine_version == secrets_engine_version_value
651661

662+
# WHEN creating another GGClient instance
663+
# THEN it already has the fields set
652664
other_client = GGClient(api_key="")
653-
assert other_client.app_version is app_version_value
654-
assert other_client.secrets_engine_version is secrets_engine_version_value
665+
assert other_client.app_version == app_version_value
666+
assert other_client.secrets_engine_version == secrets_engine_version_value
655667

656668

657-
@patch("requests.Session.request")
669+
@responses.activate
658670
def test_create_honeytoken(
659-
request_mock: Mock,
660671
client: GGClient,
661672
):
662673
"""
663674
GIVEN a ggclient
664675
WHEN calling create_honeytoken with parameters
665-
THEN the parameters are passed in the request and the returned honeytoken use the parameters
676+
THEN the parameters are passed in the request
677+
AND the returned honeytoken use the parameters
666678
"""
667-
mock_response = Mock(spec=Response)
668-
mock_response.headers = {"content-type": "application/json"}
669-
mock_response.status_code = 201
670-
mock_response.json.return_value = {
671-
"id": "d45a123f-b15d-4fea-abf6-ff2a8479de5b",
672-
"name": "honeytoken A",
673-
"description": "honeytoken used in the repository AA",
674-
"created_at": "2019-08-22T14:15:22Z",
675-
"gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/honeytokens/d45a123f-b15d-4fea-abf6-ff2a8479de5b", # noqa: E501
676-
"status": "active",
677-
"triggered_at": "2019-08-22T14:15:22Z",
678-
"revoked_at": None,
679-
"open_events_count": 2,
680-
"type": "AWS",
681-
"creator_id": 122,
682-
"revoker_id": None,
683-
"creator_api_token_id": None,
684-
"revoker_api_token_id": None,
685-
"token": {"access_token_id": "AAAA", "secret_key": "BBB"},
686-
"tags": ["publicly_exposed"],
687-
}
688-
689-
request_mock.return_value = mock_response
679+
mock_response = responses.post(
680+
url=client._url_from_endpoint("honeytokens", "v1"),
681+
content_type="application/json",
682+
status=201,
683+
json={
684+
"id": "d45a123f-b15d-4fea-abf6-ff2a8479de5b",
685+
"name": "honeytoken A",
686+
"description": "honeytoken used in the repository AA",
687+
"created_at": "2019-08-22T14:15:22Z",
688+
"gitguardian_url": "https://dashboard.gitguardian.com/workspace/1/honeytokens/d45a123f-b15d-4fea-abf6-ff2a8479de5b", # noqa: E501
689+
"status": "active",
690+
"triggered_at": "2019-08-22T14:15:22Z",
691+
"revoked_at": None,
692+
"open_events_count": 2,
693+
"type": "AWS",
694+
"creator_id": 122,
695+
"revoker_id": None,
696+
"creator_api_token_id": None,
697+
"revoker_api_token_id": None,
698+
"token": {"access_token_id": "AAAA", "secret_key": "BBB"},
699+
"tags": ["publicly_exposed"],
700+
},
701+
)
690702

691703
result = client.create_honeytoken(
692704
name="honeytoken A",
693705
description="honeytoken used in the repository AA",
694706
type_="AWS",
695707
)
696708

697-
assert request_mock.called
709+
assert mock_response.call_count == 1
698710
assert isinstance(result, HoneytokenResponse)
699711

700712

701-
@patch("requests.Session.request")
713+
@responses.activate
702714
def test_create_honeytoken_error(
703-
request_mock: Mock,
704715
client: GGClient,
705716
):
706717
"""
707718
GIVEN a ggclient
708719
WHEN calling create_honeytoken with parameters without the right access
709-
THEN I get a Detail objects containing the error detail
720+
THEN I get a Detail object containing the error detail
710721
"""
711-
mock_response = Mock(spec=Response)
712-
mock_response.headers = {"content-type": "application/json"}
713-
mock_response.status_code = 400
714-
mock_response.json.return_value = {
715-
"detail": "Not authorized",
716-
}
717-
718-
request_mock.return_value = mock_response
722+
mock_response = responses.post(
723+
url=client._url_from_endpoint("honeytokens", "v1"),
724+
content_type="application/json",
725+
status=400,
726+
json={
727+
"detail": "Not authorized",
728+
},
729+
)
719730

720731
result = client.create_honeytoken(
721732
name="honeytoken A",
722733
description="honeytoken used in the repository AA",
723734
type_="AWS",
724735
)
725736

726-
assert request_mock.called
737+
assert mock_response.call_count == 1
727738
assert isinstance(result, Detail)
728-
result.status_code == 400

0 commit comments

Comments
 (0)