"""REQ-110: Wiz adapter real API client + graceful degrade.""" import json import os import sys from pathlib import Path from unittest import mock import pytest ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT)) from adapters.wiz.wiz_adapter import ( WizClient, fetch_and_adapt, adapt, is_configured, _to_pcr, _emit_not_configured, ) # A recorded Wiz GraphQL fixture (response shape). WIZ_FIXTURE = { "data": { "issues": { "nodes": [ { "id": "issue-1", "severity": "CRITICAL", "title": "Public S3 bucket", "status": "OPEN", "entity": {"id": "arn:aws:s3:::x", "name": "x", "type": "S3_BUCKET", "cloudPlatform": "AWS"}, "control": {"id": "c1", "name": "no-public-buckets"}, "createdAt": "2026-07-20T00:00:00Z", }, { "id": "issue-2", "severity": "HIGH", "title": "Missing encryption", "status": "OPEN", "entity": {"id": "arn:aws:s3:::y", "name": "y", "type": "S3_BUCKET", "cloudPlatform": "AWS"}, "control": {"id": "c2", "name": "require-encryption"}, "createdAt": "2026-07-21T00:00:00Z", }, ], "pageInfo": {"hasNextPage": False, "endCursor": None}, } } } def test_wiz_client_requires_token_and_url(monkeypatch): monkeypatch.delenv("WIZ_API_TOKEN", raising=False) monkeypatch.delenv("WIZ_API_URL", raising=False) with pytest.raises(RuntimeError): WizClient() def test_fetch_and_adapt_with_mock_client(): """fetch_and_adapt translates Wiz issues to PolicyCheckResult via the real client.""" client = mock.MagicMock(spec=WizClient) client.fetch_issues.return_value = WIZ_FIXTURE["data"]["issues"]["nodes"] pcrs = fetch_and_adapt("contract-1", client=client) assert len(pcrs) == 2 assert pcrs[0]["engine"] == "wiz" assert pcrs[0]["ruleId"] == "no-public-buckets" assert pcrs[0]["severity"] == "critical" assert pcrs[0]["result"] == "fail" assert pcrs[1]["ruleId"] == "require-encryption" assert pcrs[1]["severity"] == "high" def test_fetch_and_adapt_graceful_degrade_when_unconfigured(monkeypatch): monkeypatch.delenv("WIZ_API_TOKEN", raising=False) monkeypatch.delenv("WIZ_API_URL", raising=False) pcrs = fetch_and_adapt("contract-2") assert len(pcrs) == 1 assert pcrs[0]["ruleId"] == "WIZ_NOT_CONFIGURED" assert pcrs[0]["result"] == "skipped" def test_wiz_client_pagination(monkeypatch): """Pagination follows pageInfo.hasNextPage + endCursor.""" monkeypatch.setenv("WIZ_API_TOKEN", "tok") monkeypatch.setenv("WIZ_API_URL", "https://api.wiz.io") client = WizClient() page1 = { "data": {"issues": {"nodes": [{"id": "i1", "severity": "HIGH", "title": "t1", "status": "OPEN", "entity": {}, "control": {}}], "pageInfo": {"hasNextPage": True, "endCursor": "cursor1"}}} } page2 = { "data": {"issues": {"nodes": [{"id": "i2", "severity": "LOW", "title": "t2", "status": "OPEN", "entity": {}, "control": {}}], "pageInfo": {"hasNextPage": False, "endCursor": None}}} } with mock.patch.object(client, "_post", side_effect=[page1, page2]): issues = client.fetch_issues() assert len(issues) == 2 def test_adapt_accepts_graphql_response_shape(tmp_path): """adapt() accepts a full GraphQL response shape ({data:{issues:{nodes:[...]}}}).""" fixture = tmp_path / "wiz.json" fixture.write_text(json.dumps(WIZ_FIXTURE)) pcrs = adapt(str(fixture), "contract-3") assert len(pcrs) == 2 assert pcrs[0]["engine"] == "wiz" def test_adapt_accepts_bare_list(tmp_path): fixture = tmp_path / "wiz.json" fixture.write_text(json.dumps(WIZ_FIXTURE["data"]["issues"]["nodes"])) pcrs = adapt(str(fixture), "contract-4") assert len(pcrs) == 2 def test_adapt_empty_issues_emits_not_configured(tmp_path): fixture = tmp_path / "wiz.json" fixture.write_text(json.dumps({"data": {"issues": {"nodes": []}}})) pcrs = adapt(str(fixture), "contract-5") assert len(pcrs) == 1 assert pcrs[0]["ruleId"] == "WIZ_NOT_CONFIGURED" def test_to_pcr_maps_severity_and_result(): issue = {"id": "x", "severity": "INFORMATIONAL", "status": "RESOLVED", "title": "t", "entity": {"id": "r"}, "control": {"name": "rule"}} pcr = _to_pcr(issue, "c") assert pcr["severity"] == "info" assert pcr["result"] == "pass" assert pcr["ruleId"] == "rule" def test_is_configured(monkeypatch): monkeypatch.setenv("WIZ_API_TOKEN", "tok") monkeypatch.setenv("WIZ_API_URL", "https://api.wiz.io") assert is_configured() is True monkeypatch.delenv("WIZ_API_URL", raising=False) assert is_configured() is False