-
Notifications
You must be signed in to change notification settings - Fork 1
[GPCAPIM-260]-[Steel Thread integration testing]-[RP] #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -234,6 +234,14 @@ jobs: | |||||
| /cds/gateway/dev/mtls/client1-key-public | ||||||
| name-transformation: lowercase | ||||||
|
|
||||||
| # Prepare cert files for the following test suites | ||||||
| - name: Prepare mTLS cert files for tests | ||||||
| if: github.event.action != 'closed' | ||||||
| run: | | ||||||
| printf '%s' "$_cds_gateway_dev_mtls_client1_key_secret" > /tmp/client1-key.pem | ||||||
| printf '%s' "$_cds_gateway_dev_mtls_client1_key_public" > /tmp/client1-cert.pem | ||||||
| chmod 600 /tmp/client1-key.pem /tmp/client1-cert.pem | ||||||
|
|
||||||
| - name: Smoke test preview URL | ||||||
| if: github.event.action != 'closed' | ||||||
| id: smoke-test | ||||||
|
|
@@ -247,9 +255,6 @@ jobs: | |||||
| exit 0 | ||||||
| fi | ||||||
|
|
||||||
| # Reachability check: allow 404 (app routes might not exist yet) but fail otherwise | ||||||
| printf '%s' "$_cds_gateway_dev_mtls_client1_key_secret" > /tmp/client1-key.pem | ||||||
| printf '%s' "$_cds_gateway_dev_mtls_client1_key_public" > /tmp/client1-cert.pem | ||||||
| STATUS=$(curl \ | ||||||
| --cert /tmp/client1-cert.pem \ | ||||||
| --key /tmp/client1-key.pem \ | ||||||
|
|
@@ -258,8 +263,6 @@ jobs: | |||||
| --write-out '%{http_code}' \ | ||||||
| --head \ | ||||||
| --max-time 30 "$PREVIEW_URL"/health || true) | ||||||
| rm -f /tmp/client1-key.pem | ||||||
| rm -f /tmp/client1-cert.pem | ||||||
|
|
||||||
| if [ "$STATUS" = "404" ]; then | ||||||
| echo "Preview responded with expected 404" | ||||||
|
|
@@ -285,6 +288,156 @@ jobs: | |||||
| echo "http_result=unexpected-status" >> "$GITHUB_OUTPUT" | ||||||
| exit 0 | ||||||
|
|
||||||
| # ---------- QUALITY CHECKS (Test Suites) ---------- | ||||||
|
|
||||||
| # UNIT TESTS | ||||||
| - name: Run unit tests | ||||||
| if: github.event.action != 'closed' | ||||||
| run: make test-unit | ||||||
|
|
||||||
| - name: Upload unit test results | ||||||
| if: always() | ||||||
| uses: actions/upload-artifact@v5 | ||||||
| with: | ||||||
| name: unit-test-results | ||||||
| path: gateway-api/test-artefacts/ | ||||||
| retention-days: 30 | ||||||
|
|
||||||
| - name: Check unit-tests.xml exists | ||||||
| id: check-unit | ||||||
| if: always() | ||||||
| run: | | ||||||
| [ -f "gateway-api/test-artefacts/unit-tests.xml" ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
|
|
||||||
| - name: Publish unit test results to summary | ||||||
| if: ${{ always() && steps.check-unit.outputs.exists == 'true' }} | ||||||
| uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 | ||||||
| with: | ||||||
| paths: gateway-api/test-artefacts/unit-tests.xml | ||||||
|
|
||||||
| # CONTRACT TESTS | ||||||
| - name: Run contract tests against preview | ||||||
| if: github.event.action != 'closed' | ||||||
| env: | ||||||
| BASE_URL: ${{ steps.tf-output.outputs.preview_url }} | ||||||
| MTLS_CERT: /tmp/client1-cert.pem | ||||||
| MTLS_KEY: /tmp/client1-key.pem | ||||||
| run: make test-contract | ||||||
|
|
||||||
| - name: Upload contract test results | ||||||
| if: always() | ||||||
| uses: actions/upload-artifact@v5 | ||||||
| with: | ||||||
| name: contract-test-results | ||||||
| path: gateway-api/test-artefacts/ | ||||||
| retention-days: 30 | ||||||
|
|
||||||
| - name: Check contract-tests.xml exists | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above would we ever expect the |
||||||
| id: check-contract | ||||||
| if: always() | ||||||
| run: | | ||||||
| [ -f "gateway-api/test-artefacts/contract-tests.xml" ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
|
|
||||||
| - name: Publish contract test results to summary | ||||||
| if: ${{ always() && steps.check-contract.outputs.exists == 'true' }} | ||||||
| uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 | ||||||
| with: | ||||||
| paths: gateway-api/test-artefacts/contract-tests.xml | ||||||
|
|
||||||
| # SCHEMA TESTS | ||||||
| - name: Run schema validation against preview | ||||||
| if: github.event.action != 'closed' | ||||||
| env: | ||||||
| BASE_URL: ${{ steps.tf-output.outputs.preview_url }} | ||||||
| MTLS_CERT: /tmp/client1-cert.pem | ||||||
| MTLS_KEY: /tmp/client1-key.pem | ||||||
| run: make test-schema | ||||||
|
|
||||||
| - name: Upload schema test results | ||||||
| if: always() | ||||||
| uses: actions/upload-artifact@v5 | ||||||
| with: | ||||||
| name: schema-test-results | ||||||
| path: gateway-api/test-artefacts/ | ||||||
| retention-days: 30 | ||||||
|
|
||||||
| - name: Check schema-tests.xml exists | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, I think |
||||||
| id: check-schema | ||||||
| if: always() | ||||||
| run: | | ||||||
| [ -f "gateway-api/test-artefacts/schema-tests.xml" ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
| - name: Publish schema test results to summary | ||||||
| if: ${{ always() && steps.check-schema.outputs.exists == 'true' }} | ||||||
| uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 | ||||||
| with: | ||||||
| paths: gateway-api/test-artefacts/schema-tests.xml | ||||||
|
|
||||||
| # INTEGRATION TESTS | ||||||
| - name: Run integration tests against preview | ||||||
| if: github.event.action != 'closed' | ||||||
| env: | ||||||
| BASE_URL: ${{ steps.tf-output.outputs.preview_url }} | ||||||
| MTLS_CERT: /tmp/client1-cert.pem | ||||||
| MTLS_KEY: /tmp/client1-key.pem | ||||||
| run: make test-integration | ||||||
|
|
||||||
| - name: Upload integration test results | ||||||
| if: always() | ||||||
| uses: actions/upload-artifact@v5 | ||||||
| with: | ||||||
| name: integration-test-results | ||||||
| path: gateway-api/test-artefacts/ | ||||||
| retention-days: 30 | ||||||
|
|
||||||
| - name: Check integration-tests.xml exists | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, I think |
||||||
| id: check-integration | ||||||
| if: always() | ||||||
| run: | | ||||||
| [ -f "gateway-api/test-artefacts/integration-tests.xml" ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
| - name: Publish integration test results to summary | ||||||
| if: ${{ always() && steps.check-integration.outputs.exists == 'true' }} | ||||||
| uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 | ||||||
| with: | ||||||
| paths: gateway-api/test-artefacts/integration-tests.xml | ||||||
|
|
||||||
| # ACCEPTANCE TESTS | ||||||
| - name: Run acceptance tests against preview | ||||||
| if: github.event.action != 'closed' | ||||||
| env: | ||||||
| BASE_URL: ${{ steps.tf-output.outputs.preview_url }} | ||||||
| MTLS_CERT: /tmp/client1-cert.pem | ||||||
| MTLS_KEY: /tmp/client1-key.pem | ||||||
| run: make test-acceptance | ||||||
|
|
||||||
| - name: Upload acceptance test results | ||||||
| if: always() | ||||||
| uses: actions/upload-artifact@v5 | ||||||
| with: | ||||||
| name: acceptance-test-results | ||||||
| path: gateway-api/test-artefacts/ | ||||||
| retention-days: 30 | ||||||
|
|
||||||
| - name: Check acceptance-tests.xml exists | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above, I think |
||||||
| id: check-acceptance | ||||||
| if: always() | ||||||
| run: | | ||||||
| [ -f "gateway-api/test-artefacts/acceptance-tests.xml" ] && echo "exists=true" >> "$GITHUB_OUTPUT" || echo "exists=false" >> "$GITHUB_OUTPUT" | ||||||
|
|
||||||
| - name: Publish acceptance test results to summary | ||||||
| if: ${{ always() && steps.check-acceptance.outputs.exists == 'true' }} | ||||||
| uses: test-summary/action@31493c76ec9e7aa675f1585d3ed6f1da69269a86 | ||||||
| with: | ||||||
| paths: gateway-api/test-artefacts/acceptance-tests.xml | ||||||
|
|
||||||
| # Cleanup after tests | ||||||
| - name: Remove mTLS temp files | ||||||
| if: github.event.action != 'closed' | ||||||
| run: rm -f /tmp/client1-key.pem /tmp/client1-cert.pem || true | ||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point would these temporary files always exist? If so, perhaps it would be better to let this step fail with an unsuccessful exit code rather than providing a default
Suggested change
|
||||||
|
|
||||||
| - name: Comment function name on PR | ||||||
| if: github.event_name == 'pull_request' && github.event.action != 'closed' | ||||||
| uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd | ||||||
|
|
@@ -368,3 +521,4 @@ jobs: | |||||
| with: | ||||||
| image-ref: ${{steps.meta.outputs.ecr_url}}:${{steps.meta.outputs.branch_name}} | ||||||
| artifact-name: trivy-sbom-${{ steps.meta.outputs.branch_name }} | ||||||
|
|
||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,94 @@ | ||
| import os | ||
| import threading | ||
| from collections.abc import Generator | ||
| from functools import partial | ||
| from http.server import BaseHTTPRequestHandler, HTTPServer | ||
| from typing import Any | ||
|
|
||
| import pytest | ||
| import requests | ||
|
|
||
|
|
||
| def get_mtls_cert() -> tuple[str, str] | None: | ||
| cert_path = os.getenv("MTLS_CERT") | ||
| key_path = os.getenv("MTLS_KEY") | ||
| if not cert_path or not key_path: | ||
| return None | ||
| return (cert_path, key_path) | ||
|
|
||
|
|
||
| class MtlsProxyHandler(BaseHTTPRequestHandler): | ||
| """ | ||
| A simple proxy that forwards requests to the target HTTPS URL | ||
| attaching the mTLS client certificates. | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| target_base: str, | ||
| cert: tuple[str, str] | None, | ||
| *args: Any, | ||
| **kwargs: Any, | ||
| ) -> None: | ||
| self.target_base = target_base | ||
| self.cert = cert | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| def do_proxy(self, method: str) -> None: | ||
| if not self.target_base: | ||
| self.send_error(500, "Target base URL not set") | ||
| return | ||
|
|
||
| url = f"{self.target_base}{self.path}" | ||
| content_length_header = self.headers.get("Content-Length") | ||
| content_length = int(content_length_header) if content_length_header else 0 | ||
| body = self.rfile.read(content_length) if content_length > 0 else None | ||
| headers = {k: v for k, v in self.headers.items() if k.lower() != "host"} | ||
|
|
||
| try: | ||
| response = requests.request( | ||
| method=method, | ||
| url=url, | ||
| headers=headers, | ||
| data=body, | ||
| cert=self.cert, | ||
| verify=False, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given we have the certificate file and key, I think we should be able to verify the certificate provided by the server here? |
||
| timeout=30, | ||
| ) | ||
|
|
||
| self.send_response(response.status_code) | ||
| for k, v in response.headers.items(): | ||
| self.send_header(k, v) | ||
| self.end_headers() | ||
| self.wfile.write(response.content) | ||
|
|
||
| except Exception as e: | ||
| self.send_error(500, f"Proxy Error: {str(e)}") | ||
|
|
||
| def do_GET(self) -> None: | ||
| self.do_proxy("GET") | ||
|
|
||
| def do_POST(self) -> None: | ||
| self.do_proxy("POST") | ||
|
|
||
| def do_PUT(self) -> None: | ||
| self.do_proxy("PUT") | ||
|
|
||
|
|
||
| @pytest.fixture(scope="module") | ||
| def mtls_proxy(base_url: str) -> Generator[str, None, None]: | ||
| """ | ||
| Spins up a local HTTP server in a separate thread. | ||
| Returns the URL of this local proxy. | ||
| """ | ||
|
|
||
| cert = get_mtls_cert() | ||
| handler_factory = partial(MtlsProxyHandler, base_url, cert) | ||
| server = HTTPServer(("localhost", 0), handler_factory) | ||
| thread = threading.Thread(target=server.serve_forever) | ||
| thread.daemon = True | ||
| thread.start() | ||
|
|
||
| yield f"http://localhost:{server.server_port}" | ||
|
|
||
| server.shutdown() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would we ever expect this
unit-tests.xmlfile to not exist? Maybe it would be nicer to just let this step fail if the file doesn't exist, as I think this would point to something else going wrong anyway?