Skip to content
Draft
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
83 changes: 83 additions & 0 deletions tests/core/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -572,3 +572,86 @@
"AuthError in async context should reference 'pdd auth login', "
f"not 'pdd login'. Got: {printed}"
)


# --- Issue #652: Cloud endpoint registry must include purchase endpoint ---

def test_cloud_endpoints_include_purchase_endpoint():
"""Test that CLOUD_ENDPOINTS includes the processPddcPurchase endpoint.

Issue #652: The purchase flow fails because processPddcPurchase is not registered
in the CLOUD_ENDPOINTS map, meaning the endpoint URL cannot be properly resolved
and requests may be routed incorrectly or use mock handlers instead.
"""
assert "processPddcPurchase" in CLOUD_ENDPOINTS, (
f"'processPddcPurchase' must be registered in CLOUD_ENDPOINTS. "
f"Available endpoints: {list(CLOUD_ENDPOINTS.keys())}"
)


def test_purchase_endpoint_url_resolves_to_production(clean_env):
"""Test that processPddcPurchase resolves to the production cloud function URL.

Issue #652: The purchase endpoint must resolve to the real cloud function URL,
not a mock or missing URL. The expected production URL is:
https://us-central1-prompt-driven-development.cloudfunctions.net/processPddcPurchase
"""
url = CloudConfig.get_endpoint_url("processPddcPurchase")

assert "us-central1-prompt-driven-development.cloudfunctions.net" in url, (

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

The string
us-central1-prompt-driven-development.cloudfunctions.net
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 2 months ago

General fix: instead of checking that the production hostname appears as a substring of the full URL, parse the URL and assert on its hostname (and, if desired, scheme/path). This follows the guidance to operate on structured URL components rather than raw substrings.

Best concrete fix here:

  • In tests/core/test_cloud.py, import urlparse (from urllib.parse) alongside existing imports.
  • In test_purchase_endpoint_url_resolves_to_production, after obtaining url, parse it with urlparse(url) and assert that parsed.hostname equals the expected production hostname.
  • Optionally, we can also assert that the scheme is https to preserve the intent that this is a production Cloud Functions URL, but the minimal change needed to address the CodeQL warning is to replace the substring check with a hostname equality check using urlparse.

Specific changes:

  1. Add from urllib.parse import urlparse after the other imports at the top of tests/core/test_cloud.py.

  2. Replace:

    url = CloudConfig.get_endpoint_url("processPddcPurchase")
    
    assert "us-central1-prompt-driven-development.cloudfunctions.net" in url, (
        f"Purchase endpoint must resolve to production cloud functions URL. Got: {url}"
    )

    with:

    url = CloudConfig.get_endpoint_url("processPddcPurchase")
    parsed = urlparse(url)
    
    assert parsed.hostname == "us-central1-prompt-driven-development.cloudfunctions.net", (
        f"Purchase endpoint must resolve to production cloud functions hostname. Got: {parsed.hostname} (URL: {url})"
    )

    leaving the subsequent checks about processPddcPurchase in the path and absence of “mock/local” substrings unchanged.

No other files or logic need to be modified.


Suggested changeset 1
tests/core/test_cloud.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/tests/core/test_cloud.py b/tests/core/test_cloud.py
--- a/tests/core/test_cloud.py
+++ b/tests/core/test_cloud.py
@@ -39,6 +39,7 @@
 import asyncio
 from unittest.mock import patch, MagicMock, AsyncMock
 from z3 import Solver, Bool, And, Not
+from urllib.parse import urlparse
 
 # Import the code under test
 from pdd.core.cloud import (
@@ -597,9 +598,11 @@
     https://us-central1-prompt-driven-development.cloudfunctions.net/processPddcPurchase
     """
     url = CloudConfig.get_endpoint_url("processPddcPurchase")
+    parsed = urlparse(url)
 
-    assert "us-central1-prompt-driven-development.cloudfunctions.net" in url, (
-        f"Purchase endpoint must resolve to production cloud functions URL. Got: {url}"
+    assert parsed.hostname == "us-central1-prompt-driven-development.cloudfunctions.net", (
+        f"Purchase endpoint must resolve to production cloud functions hostname. "
+        f"Got: {parsed.hostname} (URL: {url})"
     )
     assert "processPddcPurchase" in url, (
         f"Purchase endpoint URL must contain 'processPddcPurchase'. Got: {url}"
EOF
@@ -39,6 +39,7 @@
import asyncio
from unittest.mock import patch, MagicMock, AsyncMock
from z3 import Solver, Bool, And, Not
from urllib.parse import urlparse

# Import the code under test
from pdd.core.cloud import (
@@ -597,9 +598,11 @@
https://us-central1-prompt-driven-development.cloudfunctions.net/processPddcPurchase
"""
url = CloudConfig.get_endpoint_url("processPddcPurchase")
parsed = urlparse(url)

assert "us-central1-prompt-driven-development.cloudfunctions.net" in url, (
f"Purchase endpoint must resolve to production cloud functions URL. Got: {url}"
assert parsed.hostname == "us-central1-prompt-driven-development.cloudfunctions.net", (
f"Purchase endpoint must resolve to production cloud functions hostname. "
f"Got: {parsed.hostname} (URL: {url})"
)
assert "processPddcPurchase" in url, (
f"Purchase endpoint URL must contain 'processPddcPurchase'. Got: {url}"
Copilot is powered by AI and may make mistakes. Always verify output.
f"Purchase endpoint must resolve to production cloud functions URL. Got: {url}"
)
assert "processPddcPurchase" in url, (
f"Purchase endpoint URL must contain 'processPddcPurchase'. Got: {url}"
)
assert not any(mock_indicator in url.lower() for mock_indicator in ["mock", "localhost", "127.0.0.1"]), (
f"Purchase endpoint URL must not contain mock/local indicators. Got: {url}"
)


def test_purchase_endpoint_not_mock_fallback(clean_env):
"""Test that processPddcPurchase doesn't fall through to the unknown endpoint default.

When an endpoint is not in CLOUD_ENDPOINTS, get_endpoint_url() falls back to
/{name}. For processPddcPurchase, this means it's not explicitly registered,
which is a sign that mock handlers may be used instead of the real endpoint.
"""
# If processPddcPurchase is properly registered, its path should be in CLOUD_ENDPOINTS
endpoint_path = CLOUD_ENDPOINTS.get("processPddcPurchase")
assert endpoint_path is not None, (
"'processPddcPurchase' is not in CLOUD_ENDPOINTS — it falls through to the "
"default /{name} pattern, indicating the endpoint is not properly registered. "
"This can cause mock handlers to be used in production."
)
assert endpoint_path == "/processPddcPurchase", (
f"processPddcPurchase endpoint path should be '/processPddcPurchase'. Got: {endpoint_path}"
)


def test_cloud_endpoints_completeness_for_billing():
"""Test that all billing/payment-related endpoints are registered.

Issue #652: Mock API handlers were active in production because the payment
endpoints were not registered in the CLOUD_ENDPOINTS registry.
"""
billing_endpoints = ["processPddcPurchase"]
missing = [ep for ep in billing_endpoints if ep not in CLOUD_ENDPOINTS]
assert not missing, (
f"Billing endpoints missing from CLOUD_ENDPOINTS: {missing}. "
f"Missing endpoints may fall back to mock handlers in production."
)


def test_environment_detection_defaults_to_production(clean_env):
"""Test that environment defaults to production when no overrides are set.

Issue #652: Mock APIs were active in production, suggesting the environment
detection may not be defaulting to production correctly.
"""
# Simulate getting JWT token (which triggers _ensure_default_env)
with patch.dict(os.environ, {PDD_JWT_TOKEN_ENV: "ey.test.token"}, clear=True):
CloudConfig.get_jwt_token()
assert os.environ.get("PDD_ENV") == "prod", (
f"PDD_ENV should default to 'prod' when no overrides are set. "
f"Got: {os.environ.get('PDD_ENV')}"
)
86 changes: 84 additions & 2 deletions tests/server/test_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,5 +373,87 @@

s.add(is_blacklisted)
s.add(z3.Not(rejected))

assert s.check() == z3.unsat

assert s.check() == z3.unsat


# --- Issue #652: CORS must support production origins ---

def test_configure_cors_includes_production_origin():
"""Test that default CORS config includes the production origin (https://promptdriven.ai).

Issue #652: The purchase flow on promptdriven.ai fails with a CORS violation because
the CORS middleware only allows localhost origins by default, blocking requests from
the production domain.

This test fails on the buggy code because configure_cors() defaults to only
localhost:3000 and localhost:5173 origins.
"""
app = MagicMock(spec=FastAPI)
configure_cors(app)

call_args = app.add_middleware.call_args
kwargs = call_args[1]
origins = kwargs["allow_origins"]

assert "https://promptdriven.ai" in origins, (

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High test

The string
https://promptdriven.ai
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 2 months ago

Copilot could not generate an autofix suggestion

Copilot could not generate an autofix suggestion for this alert. Try pushing a new commit or if the problem persists contact support.

f"Production origin 'https://promptdriven.ai' must be in default CORS origins. "
f"Got: {origins}"
)


def test_configure_cors_allows_production_https():
"""Test that CORS configuration supports HTTPS production origins.

Issue #652: Browser preflight requests from https://promptdriven.ai are blocked
because Access-Control-Allow-Origin header doesn't include the production domain.
"""
app = MagicMock(spec=FastAPI)
configure_cors(app)

call_args = app.add_middleware.call_args
kwargs = call_args[1]
origins = kwargs["allow_origins"]

has_production = any(
origin.startswith("https://") and "promptdriven" in origin
for origin in origins
)
assert has_production, (
f"CORS config must include at least one production HTTPS origin for promptdriven.ai. "
f"Got only: {origins}"
)


def test_configure_cors_localhost_still_present():
"""Test that localhost origins remain after adding production origins.

Ensures backward compatibility: adding production origins should not remove
the existing localhost development origins.
"""
app = MagicMock(spec=FastAPI)
configure_cors(app)

call_args = app.add_middleware.call_args
kwargs = call_args[1]
origins = kwargs["allow_origins"]

assert "http://localhost:3000" in origins
assert "http://localhost:5173" in origins or "http://127.0.0.1:5173" in origins


def test_configure_cors_rejects_unconfigured_origin():
"""Test that CORS does not use a wildcard (*) that would allow any origin.

Security: CORS should explicitly list allowed origins, not use '*'.
"""
app = MagicMock(spec=FastAPI)
configure_cors(app)

call_args = app.add_middleware.call_args
kwargs = call_args[1]
origins = kwargs["allow_origins"]

assert "*" not in origins, (
"CORS should not use wildcard '*' — explicit origins are required for security."
)
Loading
Loading