mirror of
https://github.com/0xMarcio/cve.git
synced 2026-10-23 16:35:19 +00:00
121 lines
6.0 KiB
Python
121 lines
6.0 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
from unittest.mock import patch
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
|
|
|
import sync_pocingithub
|
|
import update_cves
|
|
|
|
|
|
class GitHubResponseTests(unittest.TestCase):
|
|
def readers(self):
|
|
client = update_cves.GitHubClient("test")
|
|
return (
|
|
("repo0", update_cves, lambda: client.fetch_readmes(["owner/repo"])),
|
|
("r0", sync_pocingithub, lambda: sync_pocingithub.describe(client, ["owner/repo"])),
|
|
)
|
|
|
|
def test_operational_graphql_errors_are_not_missing_repositories(self) -> None:
|
|
for alias, module, read in self.readers():
|
|
for error in (
|
|
{"type": "RATE_LIMITED", "message": "Rate limited"},
|
|
{"type": "INTERNAL", "message": "Internal error", "path": [alias]},
|
|
{"type": "NOT_FOUND", "message": "Nested lookup failed", "path": [alias, "root"]},
|
|
):
|
|
with self.subTest(reader=alias, error=error["message"]):
|
|
payload = {"data": {alias: None, "rateLimit": {"remaining": 100}}, "errors": [error]}
|
|
with patch.object(module, "http_json", return_value=payload):
|
|
with self.assertRaises(RuntimeError):
|
|
read()
|
|
|
|
def test_missing_repository_is_tolerated(self) -> None:
|
|
for alias, module, read in self.readers():
|
|
with self.subTest(reader=alias):
|
|
payload = {
|
|
"data": {alias: None, "rateLimit": {"remaining": 100}},
|
|
"errors": [{"type": "NOT_FOUND", "path": [alias], "message": "Repository missing"}],
|
|
}
|
|
with patch.object(module, "http_json", return_value=payload):
|
|
result = read()
|
|
self.assertFalse(result[0] if isinstance(result, tuple) else result)
|
|
|
|
def test_incomplete_response_is_not_a_missing_repository(self) -> None:
|
|
for alias, module, read in self.readers():
|
|
with self.subTest(reader=alias), patch.object(
|
|
module, "http_json", return_value={"data": {"rateLimit": {"remaining": 100}}}
|
|
):
|
|
with self.assertRaises(RuntimeError):
|
|
read()
|
|
|
|
def test_deleted_repository_does_not_discard_other_batch_results(self) -> None:
|
|
client = update_cves.GitHubClient("test")
|
|
names = ["owner/deleted", "owner/repo"]
|
|
for prefix, module, read in (
|
|
("repo", update_cves, lambda: client.fetch_readmes(names)),
|
|
("r", sync_pocingithub, lambda: sync_pocingithub.describe(client, names)),
|
|
):
|
|
payload = {
|
|
"data": {f"{prefix}0": None, f"{prefix}1": {"readmeMd": {"text": "PoC"}},
|
|
"rateLimit": {"remaining": 100}},
|
|
"errors": [{"type": "NOT_FOUND", "path": [f"{prefix}0"]}],
|
|
}
|
|
with self.subTest(reader=prefix), patch.object(module, "http_json", return_value=payload):
|
|
result = read()
|
|
self.assertEqual(set(result[0] if isinstance(result, tuple) else result), {"owner/repo"})
|
|
|
|
def test_near_exhausted_quota_aborts(self) -> None:
|
|
for alias, module, read in self.readers():
|
|
with self.subTest(reader=alias), patch.object(
|
|
module, "http_json", return_value={"data": {alias: {}, "rateLimit": {"remaining": 0}}}
|
|
):
|
|
with self.assertRaises(RuntimeError):
|
|
read()
|
|
|
|
def test_batch_transport_failure_reports_incomplete_refresh(self) -> None:
|
|
with patch.object(sys, "argv", ["sync_pocingithub.py"]), patch.dict(
|
|
os.environ, {"GITHUB_TOKEN": "test"}
|
|
), patch.object(
|
|
sync_pocingithub, "candidates",
|
|
return_value={"CVE-2026-1234": {"https://github.com/owner/repo"}},
|
|
), patch.object(sync_pocingithub, "already_linked", return_value={}), patch.object(
|
|
sync_pocingithub, "describe", side_effect=RuntimeError("API unavailable")
|
|
), patch.object(sync_pocingithub, "ensure_cve_entries", return_value=(set(), set())) as ensure, patch.object(
|
|
sync_pocingithub, "reconcile_inventory", return_value=(0, 0)
|
|
) as reconcile:
|
|
self.assertEqual(sync_pocingithub.main(), 1)
|
|
self.assertEqual(ensure.call_args.args[0], {})
|
|
self.assertEqual(reconcile.call_args.args[2], {})
|
|
|
|
def test_failed_batch_keeps_successful_sibling_publication(self) -> None:
|
|
cve = "CVE-2026-1234"
|
|
name = "healthy/widget-poc"
|
|
url = f"https://github.com/{name}"
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
path = Path(directory) / f"{cve}.md"
|
|
path.write_text("#### Github\nNo PoCs found on GitHub currently.\n")
|
|
def describe(client, names):
|
|
if names == ["failed/widget-poc"]:
|
|
raise TimeoutError("API unavailable")
|
|
return {name: {"nameWithOwner": name, "url": url, "description": f"PoC for {cve}",
|
|
"root": {"entries": [{"name": "poc.py"}]},
|
|
"readmeMd": {"text": f"Reproducer for {cve}"}}}
|
|
with patch.object(sys, "argv", ["sync"]), patch.dict(os.environ, {"GITHUB_TOKEN": "test"}), \
|
|
patch.object(sync_pocingithub, "BATCH", 1), \
|
|
patch.object(sync_pocingithub, "candidates", return_value={cve: {url, "https://github.com/failed/widget-poc"}}), \
|
|
patch.object(sync_pocingithub, "already_linked", return_value={cve: (path, set())}), \
|
|
patch.object(sync_pocingithub, "describe", side_effect=describe), \
|
|
patch.object(sync_pocingithub, "ensure_cve_entries", return_value=(set(), set())), \
|
|
patch.object(sync_pocingithub, "reconcile_inventory", return_value=(1, 0)):
|
|
self.assertEqual(sync_pocingithub.main(), 1)
|
|
self.assertIn(url, path.read_text())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|