Skip to content

feat: add integration deployment test for LangGraph HITL agent#90

Merged
mpk-droid merged 3 commits into
mainfrom
feat/RHAIENG-4644-langgraph-hitl-integration-test
May 6, 2026
Merged

feat: add integration deployment test for LangGraph HITL agent#90
mpk-droid merged 3 commits into
mainfrom
feat/RHAIENG-4644-langgraph-hitl-integration-test

Conversation

@mpk-droid
Copy link
Copy Markdown
Contributor

@mpk-droid mpk-droid commented May 4, 2026

Summary

  • Adds integration deployment test for the LangGraph Human-in-the-Loop agent (agents/langgraph/human_in_the_loop/) — builds on-cluster via build-openshift, deploys to OpenShift, validates GET /health returns {"status": "healthy", "agent_initialized": true}, and tears down via fixture finalizer
  • Introduces load_agent_name() shared utility in tests/integration/utils.py that reads the agent name from agent.yaml instead of hardcoding it (addresses PR #83 feedback from @andrewdonheiser)
  • Updates react_agent integration test to use load_agent_name() as well
  • Adds make test-integration target to HITL Makefile and adds the agent to the CI matrix

Test plan

  • make test still runs only unit tests (integration tests excluded via --ignore)
  • make test-integration runs the deployment test end-to-end on the demo cluster
  • Test tears down deployment even on failure (fixture finalizer)
  • JUnit XML output is produced at results.xml
  • CI workflow passes with both agents in the matrix — run 25452473165

Jira: RHAIENG-4644

🤖 Generated with Claude Code

Add test_deployment.py for the human_in_the_loop agent following the
same pattern as react_agent: build-openshift, deploy via Helm, validate
GET /health returns 200, and teardown via fixture finalizer.

Also introduces load_agent_name() in shared utils to read the agent name
from agent.yaml instead of hardcoding it (addresses PR #83 feedback),
and updates react_agent's test to use it.

Jira: RHAIENG-4644

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

Caution

Review failed

Failed to post review comments

📝 Walkthrough

Walkthrough

CI test matrix adds langgraph-hitl-agent for agents/langgraph/human_in_the_loop. Tests gain a load_agent_name() utility; react-agent tests now derive agent name dynamically. A new integration test and Makefile test-integration target were added for the human-in-the-loop agent; CI runs integration tests for both agents.

Changes

Deployment Test & Integration Flow

Layer / File(s) Summary
Data Shape / Utilities
tests/integration/utils.py
Adds `load_agent_name(agent_dir: str
Core Test Fixtures (react agent updated)
agents/.../react_agent/tests/integration/test_deployment.py
Removes hardcoded AGENT_NAME; adds agent_name fixture (calls load_agent_name(agent_dir)); deployed_agent fixture now accepts agent_name and uses it for image tag and route lookup.
New Agent Tests (human_in_the_loop)
agents/langgraph/human_in_the_loop/tests/integration/conftest.py, agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py
Adds conftest.py that re-exports shared fixtures and a new integration test module with fixtures: agent_dir, agent_name, _write_env_file, deployed_agent (builds image, deploys, yields route URL, ensures cleanup), and test_health_endpoint asserting /health JSON fields.
Makefile Targets / Test Invocation
agents/langgraph/human_in_the_loop/Makefile
Adds test-integration target and updates test to run pytest while ignoring tests/integration; test-integration runs only tests/integration/test_deployment.py with verbose output and writes results.xml.
CI Matrix
.github/workflows/agent-deployment-test.yaml
test-agent job matrix adds { name: langgraph-hitl-agent, dir: agents/langgraph/human_in_the_loop }, causing make test-integration and artifact upload to run for the new agent alongside the existing react agent.

Sequence Diagram

sequenceDiagram
  participant CI as "GitHub Actions (agent-deployment-test)"
  participant Make as "Agent Makefile"
  participant Registry as "Internal Image Registry"
  participant Cluster as "OpenShift Cluster"
  participant Test as "pytest (integration test)"

  CI->>Make: run `make build-openshift`
  Make->>Registry: build image & push to internal registry
  CI->>Make: run `make deploy`
  Make->>Cluster: apply manifests / create deployment & route
  Test->>Cluster: poll GET {route}/health (retry/backoff)
  Cluster-->>Test: returns JSON { status, agent_initialized }
  Test-->>CI: upload `results.xml` artifact
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding an integration deployment test for the LangGraph HITL agent.
Description check ✅ Passed The description is directly related to the changeset, providing clear context about adding integration tests, the shared utility function, and CI/CD updates.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/RHAIENG-4644-langgraph-hitl-integration-test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@mpk-droid mpk-droid self-assigned this May 4, 2026
@mpk-droid mpk-droid marked this pull request as ready for review May 4, 2026 21:24
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/integration/utils.py (1)

15-20: ⚡ Quick win

Use yaml.safe_load() instead of regex to parse agent.yaml

The regex r"^name:\s*(.+)" won't strip inline YAML comments (name: my-agent # description → captures my-agent # description) or unquote quoted values (name: "my-agent" → captures "my-agent"), which would corrupt the image tag and route name used downstream.

♻️ Proposed refactor
+import yaml
+
 def load_agent_name(agent_dir: str | Path) -> str:
-    text = (Path(agent_dir) / "agent.yaml").read_text()
-    match = re.search(r"^name:\s*(.+)", text, re.MULTILINE)
-    if not match:
+    data = yaml.safe_load((Path(agent_dir) / "agent.yaml").read_text())
+    if not isinstance(data, dict) or "name" not in data:
         raise ValueError(f"No 'name' field in {agent_dir}/agent.yaml")
-    return match.group(1).strip()
+    return str(data["name"]).strip()

Note: the Makefile AGENT_NAME shell snippet uses the same regex, so if you update the Python helper you should align the Makefile too.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/utils.py` around lines 15 - 20, The load_agent_name
function currently uses a regex that captures inline comments and quoted
strings; replace it with YAML parsing: read the agent.yaml, parse with
yaml.safe_load (import yaml), extract the 'name' key, validate it's present and
a string, and return its stripped value; raise a ValueError if missing or not a
string. Update the corresponding Makefile AGENT_NAME shell snippet to use a
YAML-aware extraction approach so both places remain consistent.
agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py (1)

31-90: ⚡ Quick win

_write_env_file, deployed_agent, and test_health_endpoint are verbatim copies of the react_agent equivalents

The only distinguishing fixture is agent_dir. Everything else — INTERNAL_REGISTRY, env-file writing, build/deploy/undeploy flow, health-check assertion — is identical. When the HITL agent gains HITL-specific env requirements (e.g., a checkpointer DB URL) or the health response schema changes, one file will drift from the other.

The shared pieces belong in tests/integration/utils.py (or a shared conftest.py), with agent_dir injected as a parameter. The per-agent test module then only needs to define agent_dir and any agent-specific assertions.

♻️ Sketch of extraction (illustrative)
# tests/integration/utils.py  (additions)
+def write_env_file(agent_dir, container_image):
+    """Write a .env file so Makefile targets can source it."""
+    missing = [v for v in ("BASE_URL", "MODEL_ID") if v not in os.environ]
+    if missing:
+        pytest.fail(
+            f"Missing required env vars: {', '.join(missing)}. "
+            "Set them in the CI workflow or export locally."
+        )
+    env_path = agent_dir / ".env"
+    env_path.write_text(
+        f"API_KEY={os.environ.get('API_KEY', 'not-needed')}\n"
+        f"BASE_URL={os.environ['BASE_URL']}\n"
+        f"MODEL_ID={os.environ['MODEL_ID']}\n"
+        f"CONTAINER_IMAGE={container_image}\n"
+    )
+    return env_path
# tests/integration/conftest.py  (shared fixture)
+INTERNAL_REGISTRY = "image-registry.openshift-image-registry.svc:5000"
+
+@pytest.fixture(scope="module")
+def deployed_agent(cluster_auth, agent_dir, agent_name):
+    ...  # single shared implementation
# agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py
-INTERNAL_REGISTRY = "image-registry.openshift-image-registry.svc:5000"
-
-def _write_env_file(...): ...
-
-@pytest.fixture(scope="module")
-def deployed_agent(...): ...

 `@pytest.fixture`(scope="module")
 def agent_dir(repo_root):
     return repo_root / "agents" / "langgraph" / "human_in_the_loop"

+# agent_name and deployed_agent come from shared conftest

 `@pytest.mark.integration`
 def test_health_endpoint(deployed_agent):
     ...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py`
around lines 31 - 90, The module duplicates shared test logic (_write_env_file,
deployed_agent fixture, and test_health_endpoint's health-check flow using
INTERNAL_REGISTRY); extract these shared helpers into a common
tests/integration/utils.py (or conftest.py) as functions/fixtures that accept
agent_dir (e.g., write_env_file(agent_dir, container_image),
deployed_agent(agent_dir, agent_name) or a parametrized fixture) and expose a
reusable health_check helper, then update this file to only provide the
agent-specific agent_dir fixture and any agent-specific assertions; ensure
references to symbols _write_env_file, deployed_agent, INTERNAL_REGISTRY, and
test_health_endpoint are replaced with the new shared imports and that
imports/fixtures are adjusted accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py`:
- Around line 31-90: The module duplicates shared test logic (_write_env_file,
deployed_agent fixture, and test_health_endpoint's health-check flow using
INTERNAL_REGISTRY); extract these shared helpers into a common
tests/integration/utils.py (or conftest.py) as functions/fixtures that accept
agent_dir (e.g., write_env_file(agent_dir, container_image),
deployed_agent(agent_dir, agent_name) or a parametrized fixture) and expose a
reusable health_check helper, then update this file to only provide the
agent-specific agent_dir fixture and any agent-specific assertions; ensure
references to symbols _write_env_file, deployed_agent, INTERNAL_REGISTRY, and
test_health_endpoint are replaced with the new shared imports and that
imports/fixtures are adjusted accordingly.

In `@tests/integration/utils.py`:
- Around line 15-20: The load_agent_name function currently uses a regex that
captures inline comments and quoted strings; replace it with YAML parsing: read
the agent.yaml, parse with yaml.safe_load (import yaml), extract the 'name' key,
validate it's present and a string, and return its stripped value; raise a
ValueError if missing or not a string. Update the corresponding Makefile
AGENT_NAME shell snippet to use a YAML-aware extraction approach so both places
remain consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: 1926fe1f-d92c-4d09-a342-db0688f2e911

📥 Commits

Reviewing files that changed from the base of the PR and between e537e34 and 32f5102.

📒 Files selected for processing (7)
  • .github/workflows/agent-deployment-test.yaml
  • agents/langgraph/human_in_the_loop/Makefile
  • agents/langgraph/human_in_the_loop/tests/integration/__init__.py
  • agents/langgraph/human_in_the_loop/tests/integration/conftest.py
  • agents/langgraph/human_in_the_loop/tests/integration/test_deployment.py
  • agents/langgraph/react_agent/tests/integration/test_deployment.py
  • tests/integration/utils.py

@mpk-droid mpk-droid requested review from kami619 and tarun-etikala May 5, 2026 17:11
Comment thread tests/integration/utils.py Outdated
Copy link
Copy Markdown
Contributor

@tarun-etikala tarun-etikala left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good Overall. Added minor comments

Addresses review feedback — regex could capture inline YAML comments or
quoted values, silently corrupting image tags and route names. PyYAML is
already a transitive dependency via LangGraph/LangChain.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/integration/utils.py`:
- Around line 17-20: The code currently coerces any YAML value to a string and
returns it, which allows invalid values like null to become "None"; update the
logic that reads data["name"] to validate that the field exists, is an instance
of str, and that stripped value is non-empty before returning it (e.g., get the
raw value from data["name"], check isinstance(value, str), let name =
value.strip(), and if not name raise ValueError with a clear message); replace
the current str(data["name"]).strip() return with this validation and raise
behavior so only a non-empty string is returned.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Enterprise

Run ID: a6627203-55fd-4cc0-942b-0a1a132a4caf

📥 Commits

Reviewing files that changed from the base of the PR and between 32f5102 and b747417.

📒 Files selected for processing (1)
  • tests/integration/utils.py

Comment on lines +17 to +20
data = yaml.safe_load((Path(agent_dir) / "agent.yaml").read_text())
if not isinstance(data, dict) or "name" not in data:
raise ValueError(f"No 'name' field in {agent_dir}/agent.yaml")
return str(data["name"]).strip()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate name type/content before returning it

Line 20 currently coerces any YAML value to string (str(data["name"])), so invalid values like null become "None" and can propagate into deployment/route names. Fail fast by requiring a non-empty string.

Suggested fix
 def load_agent_name(agent_dir: str | Path) -> str:
     data = yaml.safe_load((Path(agent_dir) / "agent.yaml").read_text())
     if not isinstance(data, dict) or "name" not in data:
         raise ValueError(f"No 'name' field in {agent_dir}/agent.yaml")
-    return str(data["name"]).strip()
+    name = data["name"]
+    if not isinstance(name, str) or not name.strip():
+        raise ValueError(f"Invalid 'name' value in {agent_dir}/agent.yaml: expected non-empty string")
+    return name.strip()

As per coding guidelines, "Focus on major issues impacting performance, readability, maintainability and security. Avoid nitpicks and avoid verbosity."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
data = yaml.safe_load((Path(agent_dir) / "agent.yaml").read_text())
if not isinstance(data, dict) or "name" not in data:
raise ValueError(f"No 'name' field in {agent_dir}/agent.yaml")
return str(data["name"]).strip()
def load_agent_name(agent_dir: str | Path) -> str:
data = yaml.safe_load((Path(agent_dir) / "agent.yaml").read_text())
if not isinstance(data, dict) or "name" not in data:
raise ValueError(f"No 'name' field in {agent_dir}/agent.yaml")
name = data["name"]
if not isinstance(name, str) or not name.strip():
raise ValueError(f"Invalid 'name' value in {agent_dir}/agent.yaml: expected non-empty string")
return name.strip()
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/integration/utils.py` around lines 17 - 20, The code currently coerces
any YAML value to a string and returns it, which allows invalid values like null
to become "None"; update the logic that reads data["name"] to validate that the
field exists, is an instance of str, and that stripped value is non-empty before
returning it (e.g., get the raw value from data["name"], check isinstance(value,
str), let name = value.strip(), and if not name raise ValueError with a clear
message); replace the current str(data["name"]).strip() return with this
validation and raise behavior so only a non-empty string is returned.

@tarun-etikala
Copy link
Copy Markdown
Contributor

LGTM

The `set -o pipefail` combined with `oc get all | head -10` causes
exit code 141 when head closes the pipe before oc finishes writing.
Add `|| true` since this step is diagnostic only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mpk-droid mpk-droid merged commit 6be61b4 into main May 6, 2026
9 checks passed
@mpk-droid mpk-droid deleted the feat/RHAIENG-4644-langgraph-hitl-integration-test branch May 6, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants