Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .secrets.baseline

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion docs/docs/testing/entra-id-e2e.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,10 @@ export AZURE_TENANT_ID="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# Test Configuration
export TEST_ENTRA_USER_PASSWORD="ContextForge2024!Test"
export TEST_ENTRA_DOMAIN="yourcompany.onmicrosoft.com"

# Real DNS passthrough for the test session (tests/conftest.py stubs all
# non-localhost DNS by default, which blackholes Graph/Entra egress)
export TESTS_DNS_PASSTHROUGH_HOSTS="login.microsoftonline.com,graph.microsoft.com"
```

### Environment Variable Reference
Expand All @@ -181,6 +185,7 @@ export TEST_ENTRA_DOMAIN="yourcompany.onmicrosoft.com"
| `AZURE_TENANT_ID` | Yes | Azure AD tenant ID |
| `TEST_ENTRA_USER_PASSWORD` | Yes | Password for dynamically created test users |
| `TEST_ENTRA_DOMAIN` | Yes | Domain for test user UPNs (e.g., `company.onmicrosoft.com`) |
| `TESTS_DNS_PASSTHROUGH_HOSTS` | Yes | Comma-separated external hosts that bypass the deterministic-DNS stub in `tests/conftest.py`. Without it, all Entra/Graph requests time out |

---

Expand All @@ -193,7 +198,7 @@ export TEST_ENTRA_DOMAIN="yourcompany.onmicrosoft.com"
source .env.test

# Run the tests
uv run pytest tests/integration/test_entra_id_integration.py -v
uv run pytest tests/integration/test_entra_id_integration.py -v --with-integration
```

### Run Specific Test Classes
Expand Down
12 changes: 11 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,12 @@ def _fixture(max_count: int, message: str = None):
# pipeline runs without requiring real network access.
_REAL_GETADDRINFO = socket.getaddrinfo
_STUB_PUBLIC_IP = "93.184.215.14" # IANA example.com
# Hostnames that must resolve via real DNS even with the global stub active.
# Opt-in via comma-separated env var; used by integration tests that talk to
# real external IdPs (e.g. Entra ID: login.microsoftonline.com, graph.microsoft.com).
_DNS_PASSTHROUGH_HOSTS = frozenset(
h.strip().lower() for h in os.environ.get("TESTS_DNS_PASSTHROUGH_HOSTS", "").split(",") if h.strip()
)


def _stub_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
Expand All @@ -580,7 +586,11 @@ def _stub_getaddrinfo(host, port, family=0, type=0, proto=0, flags=0):
patch ``mcpgateway.common.validators.socket.getaddrinfo`` (or ``socket.getaddrinfo``
directly) which takes precedence within the patch context.
"""
if host in ("localhost", "127.0.0.1", "::1", "0.0.0.0"):
# httpx/anyio resolve through anyio.getaddrinfo, which IDNA-encodes the
# hostname to bytes before reaching socket.getaddrinfo; normalize so the
# localhost/passthrough checks see a plain string.
host_str = host.decode("idna") if isinstance(host, (bytes, bytearray)) else host
if host_str in ("localhost", "127.0.0.1", "::1", "0.0.0.0") or str(host_str).lower() in _DNS_PASSTHROUGH_HOSTS:
return _REAL_GETADDRINFO(host, port, family, type, proto, flags)
return [(socket.AF_INET, socket.SOCK_STREAM, 6, "", (_STUB_PUBLIC_IP, port or 0))]

Expand Down
Loading