Skip to content

Commit 7de83a3

Browse files
author
Kevin Westphal
committed
ci: mock get API calls
The test mock of the GitGuardian API forwards GET requests implicitly to the public API. This commit introduced mocks for the GET endpoints used in tests.
1 parent e1ddb3d commit 7de83a3

File tree

5 files changed

+67
-41
lines changed

5 files changed

+67
-41
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,7 @@ jobs:
198198
needs:
199199
- lint
200200
- build
201+
- functest_api
201202
- test_github_secret_scan_action
202203
steps:
203204
- name: Login to Docker Hub

tests/functional/conftest.py

Lines changed: 45 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
import abc
22
import http.server
3+
import json
34
import shutil
45
import socketserver
56
import time
67
from multiprocessing import Event, Process, Value
78
from pathlib import Path
89
from typing import Generator
9-
from urllib.parse import urlparse
1010

1111
import pytest
12-
import requests
13-
from pygitguardian.config import DEFAULT_BASE_URI
1412

1513
from tests.repository import Repository
1614

@@ -36,32 +34,57 @@
3634
# Use this as a decorator for tests which call the `docker` binary
3735
requires_docker = pytest.mark.skipif(not HAS_DOCKER, reason="This test requires Docker")
3836

37+
# Fake responses for the mock GG API server, so that tests using mock fixtures
38+
# do not depend on the real GitGuardian API being reachable.
39+
_FAKE_METADATA_RESPONSE = json.dumps(
40+
{
41+
"version": "1.0.0",
42+
"preferences": {},
43+
"secret_scan_preferences": {
44+
"maximum_document_size": 1048576,
45+
"maximum_documents_per_scan": 20,
46+
},
47+
"remediation_messages": {
48+
"pre_commit": "",
49+
"pre_push": "",
50+
"pre_receive": "",
51+
},
52+
}
53+
).encode()
54+
55+
_FAKE_API_TOKENS_RESPONSE = json.dumps(
56+
{
57+
"id": "00000000-0000-0000-0000-000000000000",
58+
"name": "fake-token",
59+
"workspace_id": 1,
60+
"type": "personal_access_token",
61+
"status": "active",
62+
"created_at": "2020-01-01T00:00:00Z",
63+
"scopes": ["scan"],
64+
}
65+
).encode()
66+
3967

4068
class AbstractGGAPIHandler(http.server.BaseHTTPRequestHandler, metaclass=abc.ABCMeta):
4169
def do_HEAD(self):
4270
self.send_response(200)
4371

4472
def do_GET(self):
45-
# Forward all GET calls to the real server
46-
url = DEFAULT_BASE_URI + self.path.replace("/exposed", "")
47-
headers = {
48-
**self.headers,
49-
"Host": urlparse(url).netloc,
50-
}
51-
52-
response = requests.get(url, headers=headers)
53-
54-
self.send_response(response.status_code)
55-
56-
for name, value in response.headers.items():
57-
if name != "content-encoding" and name != "transfer-encoding":
58-
# Forward headers, but not content-encoding nor transfer-encoding
59-
# because our response is not compressed and/or chunked content, even if
60-
# we received it that way
61-
self.send_header(name, value)
62-
self.end_headers()
73+
# Return fake responses so mock-based tests don't depend on the real API
74+
if "metadata" in self.path:
75+
content = _FAKE_METADATA_RESPONSE
76+
elif "api_tokens" in self.path:
77+
content = _FAKE_API_TOKENS_RESPONSE
78+
else:
79+
self.send_response(418)
80+
self.end_headers()
81+
return
6382

64-
self.wfile.write(response.content)
83+
self.send_response(200)
84+
self.send_header("content-type", "application/json")
85+
self.send_header("Content-Length", str(len(content)))
86+
self.end_headers()
87+
self.wfile.write(content)
6588

6689
@abc.abstractmethod
6790
def do_POST(self):

tests/functional/secret/test_scan_invalid_api_key.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
from pathlib import Path
22

3+
import pytest
4+
35
from tests.functional.utils import run_ggshield_scan
46

57

8+
pytestmark = pytest.mark.uses_gitguardian_api
9+
610
CURRENT_DIR = Path(__file__).parent
711

812

tests/functional/secret/test_scan_prereceive.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,13 @@
99
from tests.repository import Repository
1010

1111

12-
pytestmark = pytest.mark.uses_gitguardian_api
13-
14-
1512
HOOK_CONTENT = """#!/bin/sh
1613
set -e
1714
ggshield secret scan pre-receive
1815
"""
1916

2017

18+
@pytest.mark.uses_gitguardian_api
2119
def test_scan_prereceive(tmp_path: Path) -> None:
2220
# GIVEN a remote repository
2321
remote_repo = Repository.create(tmp_path / "remote", bare=True)
@@ -47,6 +45,7 @@ def test_scan_prereceive(tmp_path: Path) -> None:
4745
assert recreate_censored_content(secret_content, GG_VALID_TOKEN) in stderr
4846

4947

48+
@pytest.mark.uses_gitguardian_api
5049
def test_scan_prereceive_branch_without_new_commits(tmp_path: Path) -> None:
5150
# GIVEN a remote repository
5251
remote_repo = Repository.create(tmp_path / "remote", bare=True)
@@ -73,6 +72,7 @@ def test_scan_prereceive_branch_without_new_commits(tmp_path: Path) -> None:
7372
local_repo.push("-u", "origin", branch_name)
7473

7574

75+
@pytest.mark.uses_gitguardian_api
7676
def test_scan_prereceive_push_force(tmp_path: Path) -> None:
7777
# GIVEN a remote repository
7878
remote_repo = Repository.create(tmp_path / "remote", bare=True)
@@ -132,6 +132,7 @@ def test_scan_prereceive_timeout(
132132
with caplog.at_level(logging.WARNING):
133133
monkeypatch.setenv("GITGUARDIAN_API_URL", slow_gitguardian_api)
134134
monkeypatch.delenv("GITGUARDIAN_INSTANCE", raising=False)
135+
monkeypatch.setenv("GITGUARDIAN_API_KEY", "dummy")
135136
local_repo.push()
136137

137138
# AND the error message contains timeout message

tests/functional/secret/test_scan_repo.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import json
2-
import os
3-
from unittest.mock import patch
42

53
import jsonschema
64
import pytest
@@ -10,9 +8,6 @@
108
from tests.repository import Repository
119

1210

13-
pytestmark = pytest.mark.uses_gitguardian_api
14-
15-
1611
LEAK_CONTENT = f"password = {GG_VALID_TOKEN}"
1712

1813

@@ -35,6 +30,7 @@ def leaky_repo(tmp_path_factory: pytest.TempPathFactory) -> Repository:
3530
return repo
3631

3732

33+
@pytest.mark.uses_gitguardian_api
3834
def test_scan_repo(leaky_repo: Repository) -> None:
3935
# GIVEN a repository with a past commit containing a leak
4036
# WHEN scanning the repo
@@ -47,6 +43,7 @@ def test_scan_repo(leaky_repo: Repository) -> None:
4743
assert recreate_censored_content(LEAK_CONTENT, GG_VALID_TOKEN) in proc.stdout
4844

4945

46+
@pytest.mark.uses_gitguardian_api
5047
def test_scan_repo_json(leaky_repo: Repository, secret_json_schema) -> None:
5148
# GIVEN a repository with a past commit containing a leak
5249
# WHEN scanning the repo
@@ -60,22 +57,21 @@ def test_scan_repo_json(leaky_repo: Repository, secret_json_schema) -> None:
6057

6158

6259
def test_scan_repo_quota_limit_reached(
63-
leaky_repo: Repository, no_quota_gitguardian_api: str, caplog
60+
leaky_repo: Repository, no_quota_gitguardian_api: str, monkeypatch, caplog
6461
) -> None:
6562
# GIVEN a repository with a past commit containing a leak
6663

6764
# WHEN scanning the repo
6865
# THEN error code is 128
69-
with patch.dict(
70-
os.environ, {**os.environ, "GITGUARDIAN_API_URL": no_quota_gitguardian_api}
71-
):
72-
proc = run_ggshield_scan(
73-
"repo",
74-
str(leaky_repo.path),
75-
"--json",
76-
expected_code=128,
77-
cwd=leaky_repo.path,
78-
)
66+
monkeypatch.setenv("GITGUARDIAN_API_URL", no_quota_gitguardian_api)
67+
monkeypatch.setenv("GITGUARDIAN_API_KEY", "dummy")
68+
proc = run_ggshield_scan(
69+
"repo",
70+
str(leaky_repo.path),
71+
"--json",
72+
expected_code=128,
73+
cwd=leaky_repo.path,
74+
)
7975

8076
# AND stderr contains an error message
8177
assert (
@@ -86,6 +82,7 @@ def test_scan_repo_quota_limit_reached(
8682
assert proc.stdout.strip() == ""
8783

8884

85+
@pytest.mark.uses_gitguardian_api
8986
def test_scan_repo_exclude_patterns(
9087
leaky_repo: Repository,
9188
) -> None:

0 commit comments

Comments
 (0)