This is an automated email from the ASF dual-hosted git repository.

imbajin pushed a commit to branch goal-test
in repository https://gitbox.apache.org/repos/asf/hugegraph-ai.git


The following commit(s) were added to refs/heads/goal-test by this push:
     new d16db33c fix(quality): address PR review findings
d16db33c is described below

commit d16db33c5509647afccae588c46c0b0767adbb76
Author: imbajin <[email protected]>
AuthorDate: Sun May 31 10:03:55 2026 +0800

    fix(quality): address PR review findings
    
    - harden CI token permissions and action pinning
    - remove cwd-dependent test setup and ambiguous fixture imports
    - cover multi-primary-key commit mapping and isolate smoke tests
    - tighten integration cleanup and external test markers
---
 .github/workflows/hugegraph-llm.yml                |  29 +-
 .github/workflows/hugegraph-python-client.yml      |  21 +-
 .../reports/final-quality-report.md                |   5 +-
 .../reports/production-change-ledger.md            |   3 +
 .../quality-program/reports/service-matrix.md      |   2 +-
 .../src/hugegraph_llm/config/models/base_config.py |   3 +-
 .../config/models/base_prompt_config.py            |  17 +-
 .../operators/hugegraph_op/commit_to_hugegraph.py  |   4 +-
 hugegraph-llm/src/tests/config/test_config.py      |  13 +
 hugegraph-llm/src/tests/conftest.py                |   6 +-
 .../tests/integration/test_core_graphrag_smoke.py  |   4 +-
 .../src/tests/integration/test_core_kg_smoke.py    |  15 +-
 .../integration/test_core_text2gremlin_smoke.py    |   6 +-
 .../tests/integration/test_hugegraph_boundary.py   |  12 +-
 .../models/embeddings/test_ollama_embedding.py     |  11 +-
 .../hugegraph_op/test_commit_to_hugegraph.py       | 271 ---------------
 .../test_commit_to_hugegraph_load_into_graph.py    | 366 +++++++++++++++++++++
 .../operators/llm_op/test_gremlin_generate.py      |   2 +-
 .../tests/operators/llm_op/test_keyword_extract.py |   2 +-
 .../llm_op/test_property_graph_extract.py          |   2 +-
 .../src/tests/api/test_gremlin.py                  |   2 +-
 .../src/tests/api/test_schema.py                   |  52 ++-
 22 files changed, 500 insertions(+), 348 deletions(-)

diff --git a/.github/workflows/hugegraph-llm.yml 
b/.github/workflows/hugegraph-llm.yml
index faa8046d..b73d48ac 100644
--- a/.github/workflows/hugegraph-llm.yml
+++ b/.github/workflows/hugegraph-llm.yml
@@ -26,6 +26,9 @@ on:
       - 'release-*'
   pull_request:
 
+permissions:
+  contents: read
+
 jobs:
   llm-unit-contract:
     runs-on: ubuntu-latest
@@ -35,10 +38,12 @@ jobs:
         python-version: ["3.10", "3.11"]
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+      with:
+        persist-credentials: false
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -48,7 +53,7 @@ jobs:
         echo "$HOME/.cargo/bin" >> $GITHUB_PATH
 
     - name: Cache dependencies
-      uses: actions/cache@v4
+      uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
       with:
         path: |
           ~/.cache/uv
@@ -69,7 +74,7 @@ jobs:
         uv run pytest hugegraph-llm/src/tests -m "unit or contract" 
--cov=hugegraph_llm --cov-report=term 
--cov-report=xml:.workflow/quality-program/coverage/llm-unit-contract.xml -q
 
     - name: Upload coverage artifact
-      uses: actions/upload-artifact@v4
+      uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
       with:
         name: llm-unit-contract-coverage-${{ matrix.python-version }}
         path: .workflow/quality-program/coverage/llm-unit-contract.xml
@@ -92,10 +97,12 @@ jobs:
           - 8080:8080
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+      with:
+        persist-credentials: false
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -105,7 +112,7 @@ jobs:
         echo "$HOME/.cargo/bin" >> $GITHUB_PATH
 
     - name: Cache dependencies
-      uses: actions/cache@v4
+      uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
       with:
         path: |
           ~/.cache/uv
@@ -147,10 +154,12 @@ jobs:
           - 8080:8080
 
     steps:
-    - uses: actions/checkout@v4
+    - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+      with:
+        persist-credentials: false
 
     - name: Set up Python ${{ matrix.python-version }}
-      uses: actions/setup-python@v5
+      uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
       with:
         python-version: ${{ matrix.python-version }}
 
@@ -160,7 +169,7 @@ jobs:
         echo "$HOME/.cargo/bin" >> $GITHUB_PATH
 
     - name: Cache dependencies
-      uses: actions/cache@v4
+      uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
       with:
         path: |
           ~/.cache/uv
diff --git a/.github/workflows/hugegraph-python-client.yml 
b/.github/workflows/hugegraph-python-client.yml
index a6150fa6..9017b69e 100644
--- a/.github/workflows/hugegraph-python-client.yml
+++ b/.github/workflows/hugegraph-python-client.yml
@@ -7,6 +7,9 @@ on:
       - 'release-*'
   pull_request:
 
+permissions:
+  contents: read
+
 jobs:
   client-unit-contract:
     runs-on: ubuntu-latest
@@ -16,10 +19,12 @@ jobs:
         python-version: ["3.10", "3.11", "3.12"]
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+        with:
+          persist-credentials: false
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -30,7 +35,7 @@ jobs:
 
       - name: Cache dependencies
         id: cache-deps
-        uses: actions/cache@v4
+        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
         with:
           path: |
             ~/.cache/uv
@@ -47,7 +52,7 @@ jobs:
           uv run pytest hugegraph-python-client/src/tests -m "unit or 
contract" --cov=pyhugegraph --cov-report=term 
--cov-report=xml:.workflow/quality-program/coverage/client-unit-contract.xml -q
 
       - name: Upload coverage artifact
-        uses: actions/upload-artifact@v4
+        uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
         with:
           name: client-unit-contract-coverage-${{ matrix.python-version }}
           path: .workflow/quality-program/coverage/client-unit-contract.xml
@@ -70,10 +75,12 @@ jobs:
           - 8080:8080
 
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+        with:
+          persist-credentials: false
 
       - name: Set up Python ${{ matrix.python-version }}
-        uses: actions/setup-python@v5
+        uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
         with:
           python-version: ${{ matrix.python-version }}
 
@@ -84,7 +91,7 @@ jobs:
 
       - name: Cache dependencies
         id: cache-deps
-        uses: actions/cache@v4
+        uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
         with:
           path: |
             ~/.cache/uv
diff --git a/.workflow/quality-program/reports/final-quality-report.md 
b/.workflow/quality-program/reports/final-quality-report.md
index 25e5313a..09b8d1fa 100644
--- a/.workflow/quality-program/reports/final-quality-report.md
+++ b/.workflow/quality-program/reports/final-quality-report.md
@@ -74,8 +74,11 @@ Final G7 sanity checks:
 | G2 | `hugegraph-python-client/src/pyhugegraph/utils/util.py` | Preserve 
backend error envelope details for non-404 HTTP errors and prefer server 
`message` over `exception`. | `uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q` |
 | G3 | 
`hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py` 
| Map primary-key `label:value` edge endpoints to server-created VIDs and raise 
explicit vertex creation errors. | `uv run pytest 
hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py::TestCommit2Graph::test_load_into_graph_raises_explicit_error_when_vertex_creation_fails
 -q`; `HUGEGRAPH_REQUIRED=true uv run pytest 
hugegraph-llm/src/tests/integration/test_hugegraph_boundary.py -v - [...]
 | G4 | `hugegraph-llm/src/hugegraph_llm/api/rag_api.py` | Map `/config/llm` to 
`llm_settings.chat_llm_type`. | `uv run pytest hugegraph-llm/src/tests/api -m 
"unit or contract" -v --tb=short` |
+| PR68 review | 
`hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py` 
| Extend primary-key endpoint mapping to multi-key vertex labels. | `uv run 
pytest 
hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph_load_into_graph.py
 -q` |
+| PR68 review | 
`hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py` | Resolve 
prompt config YAML from the package project root instead of process cwd. | `uv 
run pytest hugegraph-llm/src/tests/config/test_config.py -q` |
+| PR68 review | `hugegraph-llm/src/hugegraph_llm/config/models/base_config.py` 
| Resolve `.env` from the package project root instead of process cwd. | `uv 
run pytest hugegraph-llm/src/tests/config/test_config.py -q` |
 
-No production code changed in G5-G7.
+No production code changed in G5-G7. PR68 review fixes added the two 
production-code rows above.
 
 ## Failures, Skips, and Known Risks
 
diff --git a/.workflow/quality-program/reports/production-change-ledger.md 
b/.workflow/quality-program/reports/production-change-ledger.md
index 82893855..35b9b9d3 100644
--- a/.workflow/quality-program/reports/production-change-ledger.md
+++ b/.workflow/quality-program/reports/production-change-ledger.md
@@ -5,3 +5,6 @@
 | G2 | `hugegraph-python-client/src/pyhugegraph/utils/util.py` | Preserve 
backend error envelope details for non-404 HTTP errors and prefer server 
`message` over `exception`. | `uv run pytest 
hugegraph-python-client/src/tests/api/test_response_validation.py -q` | 
Response validation contract gap for 500 backend envelopes. | Low; narrows 
error wrapping to preserve parsed server details while keeping 404 mapping. |
 | G3 | 
`hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py` 
| Map primary-key `label:value` edge endpoints to server-created VIDs and raise 
explicit vertex creation errors before accessing `.id`. | `uv run pytest 
hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py::TestCommit2Graph::test_load_into_graph_raises_explicit_error_when_vertex_creation_fails
 -q`; `HUGEGRAPH_REQUIRED=true uv run pytest 
hugegraph-llm/src/tests/integration/test_hug [...]
 | G4 | `hugegraph-llm/src/hugegraph_llm/api/rag_api.py` | Map `/config/llm` to 
existing `llm_settings.chat_llm_type` instead of nonexistent 
`llm_settings.llm_type`. | `uv run pytest hugegraph-llm/src/tests/api -m "unit 
or contract" -v --tb=short` | API public-surface field mapping contract gap. | 
Low; aligns route with existing config schema and leaves callback arguments 
unchanged. |
+| PR68 review | 
`hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py` 
| Extend primary-key endpoint mapping to multi-key vertex labels by joining 
primary key values with `!`. | `uv run pytest 
hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph_load_into_graph.py
 -q` | Review-identified edge creation gap for multiple primary-key vertex IDs. 
| Low; only broadens fallback mapping for existing primary-key schemas. |
+| PR68 review | 
`hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py` | Resolve 
prompt config YAML from the package project root instead of process cwd. | `uv 
run pytest hugegraph-llm/src/tests/config/test_config.py -q` | Avoid 
import-time cwd mutation in test harness and make config path deterministic. | 
Medium-low; preserves the same file path when run from module root and removes 
cwd guard. |
+| PR68 review | `hugegraph-llm/src/hugegraph_llm/config/models/base_config.py` 
| Resolve `.env` from the package project root instead of process cwd. | `uv 
run pytest hugegraph-llm/src/tests/config/test_config.py -q` | Avoid writing or 
reading a caller cwd `.env` when tests import config from repo root. | 
Medium-low; preserves the same `.env` file when run from module root and 
removes cwd coupling. |
diff --git a/.workflow/quality-program/reports/service-matrix.md 
b/.workflow/quality-program/reports/service-matrix.md
index 8e44946c..95b771af 100644
--- a/.workflow/quality-program/reports/service-matrix.md
+++ b/.workflow/quality-program/reports/service-matrix.md
@@ -4,4 +4,4 @@
 |---|---|---|---|---|---|
 | HugeGraph Server | `hugegraph/hugegraph:1.7.0` | Layer B, Layer C 
graph-boundary smoke | `GET /versions` | `HUGEGRAPH_URL`, `HUGEGRAPH_REQUIRED` 
| fail if selected and required |
 | LLM providers | explicit user/provider config only | Layer D only | provider 
SDK call | provider-specific API keys | excluded from default gates |
-| Embedding/reranker/vector DB providers | explicit user/provider config only 
| Layer D only unless deterministic fake | provider-specific | 
provider-specific credentials | excluded from default gates |
+| Embedding/reranker/vector DB providers | explicit user/provider config | 
default Layer D; deterministic fake allowed | provider-specific | 
provider-specific credentials | excluded from default gates |
diff --git a/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py 
b/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py
index 5fec3a77..6953166b 100644
--- a/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py
+++ b/hugegraph-llm/src/hugegraph_llm/config/models/base_config.py
@@ -21,10 +21,11 @@ import os
 from dotenv import dotenv_values, set_key
 from pydantic_settings import BaseSettings
 
+from hugegraph_llm.utils.anchor import get_project_root
 from hugegraph_llm.utils.log import log
 
 dir_name = os.path.dirname
-env_path = os.path.join(os.getcwd(), ".env")  # Load .env from the current 
working directory
+env_path = os.path.join(get_project_root(), ".env")
 
 
 class BaseConfig(BaseSettings):
diff --git 
a/hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py 
b/hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py
index 57d8ba63..b7cd2f91 100644
--- a/hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py
+++ b/hugegraph-llm/src/hugegraph_llm/config/models/base_prompt_config.py
@@ -16,8 +16,6 @@
 # under the License.
 
 import os
-import sys
-from pathlib import Path
 
 import yaml
 
@@ -26,7 +24,8 @@ from hugegraph_llm.utils.log import log
 
 dir_name = os.path.dirname
 F_NAME = "config_prompt.yaml"
-yaml_file_path = os.path.join(os.getcwd(), "src/hugegraph_llm/resources/demo", 
F_NAME)
+PROJECT_ROOT = get_project_root()
+yaml_file_path = os.path.join(PROJECT_ROOT, 
"src/hugegraph_llm/resources/demo", F_NAME)
 
 
 class LiteralStr(str):
@@ -54,18 +53,6 @@ class BasePromptConfig:
     generate_extract_prompt_template: str = ""
 
     def ensure_yaml_file_exists(self):
-        current_dir = Path.cwd().resolve()
-        project_root = get_project_root()
-        if current_dir == project_root:
-            log.info("Current working directory is the project root, 
proceeding to run the app.")
-        else:
-            error_msg = (
-                f"Current working directory is not the project root. "
-                f"Please run this script from the project root directory: 
{project_root}\n"
-                f"Current directory: {current_dir}"
-            )
-            log.error(error_msg)
-            sys.exit(1)
         if os.path.exists(yaml_file_path):
             log.info("Loading prompt file '%s' successfully.", F_NAME)
             with open(yaml_file_path, "r", encoding="utf-8") as file:
diff --git 
a/hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py 
b/hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py
index caa33b9a..14bb3865 100644
--- 
a/hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py
+++ 
b/hugegraph-llm/src/hugegraph_llm/operators/hugegraph_op/commit_to_hugegraph.py
@@ -149,8 +149,8 @@ class Commit2Graph:
             # TODO: we could try batch add vertices first, setback to 
single-mode if failed
             explicit_id = vertex.get("id")
             mapping_id = explicit_id
-            if not mapping_id and len(primary_keys) == 1:
-                mapping_id = 
f"{input_label}:{input_properties[primary_keys[0]]}"
+            if not mapping_id and primary_keys:
+                mapping_id = 
f"{input_label}:{'!'.join(str(input_properties[pk]) for pk in primary_keys)}"
 
             if vertex_label.get("id_strategy") == "CUSTOMIZE_STRING" and 
explicit_id:
                 result = self._handle_graph_creation(
diff --git a/hugegraph-llm/src/tests/config/test_config.py 
b/hugegraph-llm/src/tests/config/test_config.py
index 9a2abf66..b5569a8f 100644
--- a/hugegraph-llm/src/tests/config/test_config.py
+++ b/hugegraph-llm/src/tests/config/test_config.py
@@ -17,6 +17,7 @@
 
 
 import unittest
+from pathlib import Path
 
 import pytest
 
@@ -31,3 +32,15 @@ class TestConfig(unittest.TestCase):
 
         nltk.data.path.append(resource_path)
         nltk.data.find("corpora/stopwords")
+
+    def test_prompt_yaml_path_is_project_root_independent(self):
+        from hugegraph_llm.config.models import base_prompt_config
+
+        expected = Path(__file__).resolve().parents[2] / "hugegraph_llm" / 
"resources" / "demo" / "config_prompt.yaml"
+        self.assertEqual(Path(base_prompt_config.yaml_file_path), expected)
+
+    def test_env_path_is_project_root_independent(self):
+        from hugegraph_llm.config.models import base_config
+
+        expected = Path(__file__).resolve().parents[3] / ".env"
+        self.assertEqual(Path(base_config.env_path), expected)
diff --git a/hugegraph-llm/src/tests/conftest.py 
b/hugegraph-llm/src/tests/conftest.py
index ec14a6e3..ac4bb867 100644
--- a/hugegraph-llm/src/tests/conftest.py
+++ b/hugegraph-llm/src/tests/conftest.py
@@ -23,14 +23,14 @@ import nltk
 
 # Get project root directory
 project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), 
"../.."))
-os.chdir(project_root)
-# Add to Python path
 sys.path.insert(0, project_root)
 # Add src directory to Python path
 src_path = os.path.join(project_root, "src")
 sys.path.insert(0, src_path)
+tests_path = os.path.join(project_root, "src", "tests")
+sys.path.insert(0, tests_path)
 
-from tests.fixtures.hugegraph_service import hugegraph_service  # noqa: E402
+from fixtures.hugegraph_service import hugegraph_service  # noqa: E402
 
 __all__ = ["hugegraph_service"]
 
diff --git a/hugegraph-llm/src/tests/integration/test_core_graphrag_smoke.py 
b/hugegraph-llm/src/tests/integration/test_core_graphrag_smoke.py
index 291b0d7c..c1fdac77 100644
--- a/hugegraph-llm/src/tests/integration/test_core_graphrag_smoke.py
+++ b/hugegraph-llm/src/tests/integration/test_core_graphrag_smoke.py
@@ -63,7 +63,9 @@ def 
test_graphrag_smoke_uses_production_vector_and_rerank_operators():
     from hugegraph_llm.operators.index_op.build_vector_index import 
BuildVectorIndex
     from hugegraph_llm.operators.index_op.vector_index_query import 
VectorIndexQuery
 
-    docs = 
json.loads(Path("src/tests/data/quality_program/graphrag_documents.json").read_text())
+    InMemoryVectorIndex.stores.clear()
+    data_file = Path(__file__).resolve().parents[1] / "data" / 
"quality_program" / "graphrag_documents.json"
+    docs = json.loads(data_file.read_text(encoding="utf-8"))
     chunks = [doc["text"] for doc in docs]
     embedding = DeterministicEmbedding()
 
diff --git a/hugegraph-llm/src/tests/integration/test_core_kg_smoke.py 
b/hugegraph-llm/src/tests/integration/test_core_kg_smoke.py
index 45c669b5..7d9721de 100644
--- a/hugegraph-llm/src/tests/integration/test_core_kg_smoke.py
+++ b/hugegraph-llm/src/tests/integration/test_core_kg_smoke.py
@@ -84,17 +84,22 @@ def configured_hugegraph(hugegraph_service):
 
     client = _make_client(hugegraph_service)
     client.graphs().clear_graph_all_data()
-    yield hugegraph_service
-    client.graphs().clear_graph_all_data()
-    for key, value in original.items():
-        setattr(huge_settings, key, value)
+    try:
+        yield hugegraph_service
+    finally:
+        try:
+            client.graphs().clear_graph_all_data()
+        finally:
+            for key, value in original.items():
+                setattr(huge_settings, key, value)
 
 
 def test_kg_construction_smoke_uses_production_code(configured_hugegraph):
     from hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph import 
Commit2Graph
     from hugegraph_llm.operators.hugegraph_op.fetch_graph_data import 
FetchGraphData
 
-    fixture = 
json.loads(Path("src/tests/data/quality_program/kg_graph_output.json").read_text())
+    fixture_file = Path(__file__).resolve().parents[1] / "data" / 
"quality_program" / "kg_graph_output.json"
+    fixture = json.loads(fixture_file.read_text(encoding="utf-8"))
     assert fixture["vertices"]
     assert fixture["edges"]
 
diff --git 
a/hugegraph-llm/src/tests/integration/test_core_text2gremlin_smoke.py 
b/hugegraph-llm/src/tests/integration/test_core_text2gremlin_smoke.py
index 06caf6a0..c0249811 100644
--- a/hugegraph-llm/src/tests/integration/test_core_text2gremlin_smoke.py
+++ b/hugegraph-llm/src/tests/integration/test_core_text2gremlin_smoke.py
@@ -19,8 +19,7 @@ import json
 from pathlib import Path
 
 import pytest
-
-from tests.fixtures.fake_llm import FakeLLM
+from fixtures.fake_llm import FakeLLM
 
 pytestmark = [pytest.mark.smoke, pytest.mark.integration]
 
@@ -30,7 +29,8 @@ def test_text2gremlin_smoke_normalizes_fake_llm_output():
     from hugegraph_llm.operators.llm_op.gremlin_generate import 
GremlinGenerateSynthesize
     from hugegraph_llm.state.ai_state import WkFlowInput
 
-    schema = 
json.loads(Path("src/tests/data/quality_program/text2gremlin_schema.json").read_text())
+    schema_file = Path(__file__).resolve().parents[1] / "data" / 
"quality_program" / "text2gremlin_schema.json"
+    schema = json.loads(schema_file.read_text(encoding="utf-8"))
     generator = GremlinGenerateSynthesize(
         llm=FakeLLM(
             [
diff --git a/hugegraph-llm/src/tests/integration/test_hugegraph_boundary.py 
b/hugegraph-llm/src/tests/integration/test_hugegraph_boundary.py
index 6184a023..4592d7b8 100644
--- a/hugegraph-llm/src/tests/integration/test_hugegraph_boundary.py
+++ b/hugegraph-llm/src/tests/integration/test_hugegraph_boundary.py
@@ -129,10 +129,14 @@ def configured_hugegraph(hugegraph_service):
 
     client = _make_client(hugegraph_service)
     client.graphs().clear_graph_all_data()
-    yield hugegraph_service
-    client.graphs().clear_graph_all_data()
-    for key, value in original.items():
-        setattr(huge_settings, key, value)
+    try:
+        yield hugegraph_service
+    finally:
+        try:
+            client.graphs().clear_graph_all_data()
+        finally:
+            for key, value in original.items():
+                setattr(huge_settings, key, value)
 
 
 def _create_quality_schema(client):
diff --git a/hugegraph-llm/src/tests/models/embeddings/test_ollama_embedding.py 
b/hugegraph-llm/src/tests/models/embeddings/test_ollama_embedding.py
index 3aca1b9a..07b5df62 100644
--- a/hugegraph-llm/src/tests/models/embeddings/test_ollama_embedding.py
+++ b/hugegraph-llm/src/tests/models/embeddings/test_ollama_embedding.py
@@ -25,19 +25,19 @@ import pytest
 from hugegraph_llm.models.embeddings.base import SimilarityMode
 from hugegraph_llm.models.embeddings.ollama import OllamaEmbedding
 
-pytestmark = pytest.mark.contract
-
 
 class TestOllamaEmbedding(unittest.TestCase):
     def setUp(self):
         self.skip_external = os.getenv("SKIP_EXTERNAL_SERVICES", 
"false").lower() == "true"
 
+    @pytest.mark.external
     @unittest.skipIf(os.getenv("SKIP_EXTERNAL_SERVICES", "false").lower() == 
"true", "Skipping external service tests")
     def test_get_text_embedding(self):
         ollama_embedding = 
OllamaEmbedding(model_name="quentinz/bge-large-zh-v1.5")
         embedding = ollama_embedding.get_text_embedding("hello world")
         print(embedding)
 
+    @pytest.mark.external
     @unittest.skipIf(os.getenv("SKIP_EXTERNAL_SERVICES", "false").lower() == 
"true", "Skipping external service tests")
     def test_get_cosine_similarity(self):
         ollama_embedding = 
OllamaEmbedding(model_name="quentinz/bge-large-zh-v1.5")
@@ -46,6 +46,7 @@ class TestOllamaEmbedding(unittest.TestCase):
         similarity = OllamaEmbedding.similarity(embedding1, embedding2, 
SimilarityMode.DEFAULT)
         print(similarity)
 
+    @pytest.mark.contract
     def test_async_get_texts_embeddings_preserves_batch_order(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.async_client = AsyncMock()
@@ -65,6 +66,7 @@ class TestOllamaEmbedding(unittest.TestCase):
 
         asyncio.run(run_async_test())
 
+    @pytest.mark.contract
     def test_async_get_text_embedding_requires_embeddings_key(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.async_client = AsyncMock()
@@ -78,6 +80,7 @@ class TestOllamaEmbedding(unittest.TestCase):
 
         asyncio.run(run_async_test())
 
+    @pytest.mark.contract
     def test_get_texts_embeddings_requires_embeddings_key(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.client = MagicMock()
@@ -86,6 +89,7 @@ class TestOllamaEmbedding(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, "missing 'embeddings'"):
             ollama_embedding.get_texts_embeddings(["a"])
 
+    @pytest.mark.contract
     def test_get_texts_embeddings_requires_embed_method(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.client = object()
@@ -93,6 +97,7 @@ class TestOllamaEmbedding(unittest.TestCase):
         with self.assertRaisesRegex(AttributeError, "required 'embed' method"):
             ollama_embedding.get_texts_embeddings(["a"])
 
+    @pytest.mark.contract
     def test_get_texts_embeddings_requires_non_empty_embeddings(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.client = MagicMock()
@@ -101,6 +106,7 @@ class TestOllamaEmbedding(unittest.TestCase):
         with self.assertRaisesRegex(ValueError, "returned no embeddings"):
             ollama_embedding.get_texts_embeddings(["a"])
 
+    @pytest.mark.contract
     def test_async_get_text_embedding_requires_non_empty_embeddings(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.async_client = AsyncMock()
@@ -114,6 +120,7 @@ class TestOllamaEmbedding(unittest.TestCase):
 
         asyncio.run(run_async_test())
 
+    @pytest.mark.contract
     def test_async_get_text_embedding_requires_embed_method(self):
         ollama_embedding = OllamaEmbedding(model="test-model")
         ollama_embedding.async_client = object()
diff --git 
a/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py 
b/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py
index d89936d8..fbd9e3a5 100644
--- a/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py
+++ b/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph.py
@@ -23,7 +23,6 @@ import pytest
 from pyhugegraph.utils.exceptions import CreateError, NotFoundError
 
 from hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph import 
Commit2Graph
-from hugegraph_llm.operators.llm_op.property_graph_extract import 
PropertyGraphExtract
 
 pytestmark = [pytest.mark.unit]
 
@@ -301,276 +300,6 @@ class TestCommit2Graph(unittest.TestCase):
         # Verify that edgeLabel was called for each edge label
         self.assertEqual(schema_mocks["edge_label"].call_count, 1)  # 1 edge 
label
 
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._check_property_data_type")
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def test_load_into_graph(self, mock_handle_graph_creation, 
mock_check_property_data_type):
-        """Test load_into_graph method."""
-        # Setup mocks
-        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
-        mock_check_property_data_type.return_value = True
-
-        # Create vertices with proper data types according to schema
-        vertices = [
-            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
67}},
-            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
1994}},
-        ]
-
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {"role": "Forrest Gump"},
-                "outV": "person:Tom Hanks",  # Use the format expected by the 
implementation
-                "inV": "movie:Forrest Gump",  # Use the format expected by the 
implementation
-            }
-        ]
-
-        # Call the method
-        self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        # Verify that _handle_graph_creation was called for each vertex and 
edge
-        self.assertEqual(mock_handle_graph_creation.call_count, 3)  # 2 
vertices + 1 edge
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def test_load_into_graph_with_data_type_validation_success(self, 
mock_handle_graph_creation):
-        """Test load_into_graph method with successful data type validation."""
-        # Setup mocks
-        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
-
-        # Create vertices with correct data types matching schema expectations
-        vertices = [
-            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
67}},  # age: INT -> int
-            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
1994}},  # year: INT -> int
-        ]
-
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {"role": "Forrest Gump"},  # role: TEXT -> str
-                "outV": "person:Tom Hanks",
-                "inV": "movie:Forrest Gump",
-            }
-        ]
-
-        # Call the method - should succeed with correct data types
-        self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        # Verify that _handle_graph_creation was called for each vertex and 
edge
-        self.assertEqual(mock_handle_graph_creation.call_count, 3)  # 2 
vertices + 1 edge
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def test_load_into_graph_maps_llm_vertex_ids_to_created_vertex_ids(self, 
mock_handle_graph_creation):
-        """Test edges use server-created vertex ids when LLM ids differ."""
-        mock_handle_graph_creation.side_effect = [
-            MagicMock(id="1:Tom Hanks"),
-            MagicMock(id="2:Forrest Gump"),
-            MagicMock(id="edge_id"),
-        ]
-
-        vertices = [
-            {
-                "id": "person:Tom Hanks",
-                "label": "person",
-                "properties": {"name": "Tom Hanks", "age": 67},
-            },
-            {
-                "id": "movie:Forrest Gump",
-                "label": "movie",
-                "properties": {"title": "Forrest Gump", "year": 1994},
-            },
-        ]
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {"role": "Forrest Gump"},
-                "outV": "person:Tom Hanks",
-                "inV": "movie:Forrest Gump",
-            }
-        ]
-
-        self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        self.assertEqual(vertices[0]["id"], "1:Tom Hanks")
-        self.assertEqual(vertices[1]["id"], "2:Forrest Gump")
-        mock_handle_graph_creation.assert_any_call(
-            self.commit2graph.client.graph().addEdge,
-            "acted_in",
-            "1:Tom Hanks",
-            "2:Forrest Gump",
-            {"role": "Forrest Gump"},
-        )
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def test_load_into_graph_uses_explicit_customize_string_ids(self, 
mock_handle_graph_creation):
-        """Test custom string ids are passed to HugeGraph when schema requires 
them."""
-        mock_handle_graph_creation.side_effect = [
-            MagicMock(id="Tom Hanks"),
-            MagicMock(id="Forrest Gump"),
-            MagicMock(id="edge_id"),
-        ]
-        schema = {
-            "propertykeys": [
-                {"name": "name", "data_type": "TEXT", "cardinality": "SINGLE"},
-                {"name": "title", "data_type": "TEXT", "cardinality": 
"SINGLE"},
-            ],
-            "vertexlabels": [
-                {
-                    "id": 7,
-                    "name": "person",
-                    "id_strategy": "CUSTOMIZE_STRING",
-                    "primary_keys": ["name"],
-                    "properties": ["name"],
-                    "nullable_keys": [],
-                },
-                {
-                    "id": 8,
-                    "name": "movie",
-                    "id_strategy": "CUSTOMIZE_STRING",
-                    "primary_keys": ["title"],
-                    "properties": ["title"],
-                    "nullable_keys": [],
-                },
-            ],
-            "edgelabels": [{"name": "acted_in", "properties": [], 
"source_label": "person", "target_label": "movie"}],
-        }
-        vertices = [
-            {"id": "Tom Hanks", "label": "person", "properties": {"name": "Tom 
Hanks"}},
-            {"id": "Forrest Gump", "label": "movie", "properties": {"title": 
"Forrest Gump"}},
-        ]
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {},
-                "outV": "Tom Hanks",
-                "inV": "Forrest Gump",
-            }
-        ]
-
-        self.commit2graph.load_into_graph(vertices, edges, schema)
-
-        mock_handle_graph_creation.assert_any_call(
-            self.commit2graph.client.graph().addVertex,
-            "person",
-            {"name": "Tom Hanks"},
-            id="Tom Hanks",
-        )
-        mock_handle_graph_creation.assert_any_call(
-            self.commit2graph.client.graph().addVertex,
-            "movie",
-            {"title": "Forrest Gump"},
-            id="Forrest Gump",
-        )
-        mock_handle_graph_creation.assert_any_call(
-            self.commit2graph.client.graph().addEdge,
-            "acted_in",
-            "Tom Hanks",
-            "Forrest Gump",
-            {},
-        )
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def 
test_load_into_graph_accepts_normalized_extraction_without_item_type(self, 
mock_handle_graph_creation):
-        """Test normalized LLM output without type fields can be committed."""
-        mock_handle_graph_creation.side_effect = [
-            MagicMock(id="1:Tom Hanks"),
-            MagicMock(id="2:Forrest Gump"),
-            MagicMock(id="edge_id"),
-        ]
-        llm_output = """{
-            "vertices": [
-            {
-                "id": "person:Tom Hanks",
-                "label": "person",
-                "properties": {
-                    "name": "Tom Hanks",
-                    "age": 67
-                }
-            },
-            {
-                "id": "movie:Forrest Gump",
-                "label": "movie",
-                "properties": {
-                    "title": "Forrest Gump",
-                    "year": 1994
-                }
-            }
-            ],
-            "edges": [
-            {
-                "label": "acted_in",
-                "outV": "person:Tom Hanks",
-                "outVLabel": "person",
-                "inV": "movie:Forrest Gump",
-                "inVLabel": "movie",
-                "properties": {
-                    "role": "Forrest Gump"
-                }
-            }
-            ]
-        }"""
-
-        items = 
PropertyGraphExtract(llm=MagicMock())._extract_and_filter_label(self.schema, 
llm_output)
-        vertices = [item for item in items if item["type"] == "vertex"]
-        edges = [item for item in items if item["type"] == "edge"]
-        self.assertEqual(edges[0]["outV"], "1:Tom Hanks")
-        self.assertEqual(edges[0]["inV"], "2:Forrest Gump")
-
-        self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        mock_handle_graph_creation.assert_any_call(
-            self.commit2graph.client.graph().addEdge,
-            "acted_in",
-            "1:Tom Hanks",
-            "2:Forrest Gump",
-            {"role": "Forrest Gump"},
-        )
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def 
test_load_into_graph_raises_explicit_error_when_vertex_creation_fails(self, 
mock_handle_graph_creation):
-        """Test failed vertex creation is reported before edge creation."""
-        mock_handle_graph_creation.return_value = None
-
-        vertices = [{"label": "person", "properties": {"name": "Tom Hanks", 
"age": 67}}]
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {"role": "Forrest Gump"},
-                "outV": "person:Tom Hanks",
-                "inV": "movie:Forrest Gump",
-            }
-        ]
-
-        with self.assertRaisesRegex(ValueError, "Failed to create vertex"):
-            self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        mock_handle_graph_creation.assert_called_once()
-
-    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
-    def test_load_into_graph_with_data_type_validation_failure(self, 
mock_handle_graph_creation):
-        """Test load_into_graph method with data type validation failure."""
-        # Setup mocks
-        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
-
-        # Create vertices with incorrect data types (strings for INT fields)
-        vertices = [
-            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
"67"}},  # age should be int, not str
-            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
"1994"}},  # year should be int, not str
-        ]
-
-        edges = [
-            {
-                "label": "acted_in",
-                "properties": {"role": "Forrest Gump"},
-                "outV": "person:Tom Hanks",
-                "inV": "movie:Forrest Gump",
-            }
-        ]
-
-        # Call the method - should skip vertices due to data type validation 
failure
-        self.commit2graph.load_into_graph(vertices, edges, self.schema)
-
-        # Verify that _handle_graph_creation was called only for the edge 
(vertices were skipped)
-        self.assertEqual(mock_handle_graph_creation.call_count, 1)  # Only 1 
edge, vertices skipped
-
     def test_check_property_data_type_success(self):
         """Test _check_property_data_type method with valid data types."""
         # Test TEXT type
diff --git 
a/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph_load_into_graph.py
 
b/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph_load_into_graph.py
new file mode 100644
index 00000000..6605bec2
--- /dev/null
+++ 
b/hugegraph-llm/src/tests/operators/hugegraph_op/test_commit_to_hugegraph_load_into_graph.py
@@ -0,0 +1,366 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# pylint: disable=protected-access,no-member
+import unittest
+from unittest.mock import MagicMock, patch
+
+import pytest
+
+from hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph import 
Commit2Graph
+from hugegraph_llm.operators.llm_op.property_graph_extract import 
PropertyGraphExtract
+
+pytestmark = [pytest.mark.unit]
+
+
+class TestCommit2GraphLoadIntoGraph(unittest.TestCase):
+    def setUp(self):
+        self.mock_client = MagicMock()
+        self.mock_schema = MagicMock()
+        self.mock_client.schema.return_value = self.mock_schema
+
+        with patch(
+            
"hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.PyHugeClient", 
return_value=self.mock_client
+        ):
+            self.commit2graph = Commit2Graph()
+
+        self.schema = {
+            "propertykeys": [
+                {"name": "name", "data_type": "TEXT", "cardinality": "SINGLE"},
+                {"name": "age", "data_type": "INT", "cardinality": "SINGLE"},
+                {"name": "title", "data_type": "TEXT", "cardinality": 
"SINGLE"},
+                {"name": "year", "data_type": "INT", "cardinality": "SINGLE"},
+                {"name": "role", "data_type": "TEXT", "cardinality": "SINGLE"},
+            ],
+            "vertexlabels": [
+                {
+                    "id": 1,
+                    "name": "person",
+                    "properties": ["name", "age"],
+                    "primary_keys": ["name"],
+                    "nullable_keys": ["age"],
+                    "id_strategy": "PRIMARY_KEY",
+                },
+                {
+                    "id": 2,
+                    "name": "movie",
+                    "properties": ["title", "year"],
+                    "primary_keys": ["title"],
+                    "nullable_keys": ["year"],
+                    "id_strategy": "PRIMARY_KEY",
+                },
+            ],
+            "edgelabels": [
+                {"name": "acted_in", "properties": ["role"], "source_label": 
"person", "target_label": "movie"}
+            ],
+        }
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._check_property_data_type")
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def test_load_into_graph(self, mock_handle_graph_creation, 
mock_check_property_data_type):
+        """Test load_into_graph method."""
+        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
+        mock_check_property_data_type.return_value = True
+
+        vertices = [
+            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
67}},
+            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
1994}},
+        ]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {"role": "Forrest Gump"},
+                "outV": "person:Tom Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        self.assertEqual(mock_handle_graph_creation.call_count, 3)
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def test_load_into_graph_with_data_type_validation_success(self, 
mock_handle_graph_creation):
+        """Test load_into_graph method with successful data type validation."""
+        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
+
+        vertices = [
+            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
67}},
+            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
1994}},
+        ]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {"role": "Forrest Gump"},
+                "outV": "person:Tom Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        self.assertEqual(mock_handle_graph_creation.call_count, 3)
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def test_load_into_graph_maps_llm_vertex_ids_to_created_vertex_ids(self, 
mock_handle_graph_creation):
+        """Test edges use server-created vertex ids when LLM ids differ."""
+        mock_handle_graph_creation.side_effect = [
+            MagicMock(id="1:Tom Hanks"),
+            MagicMock(id="2:Forrest Gump"),
+            MagicMock(id="edge_id"),
+        ]
+
+        vertices = [
+            {"id": "person:Tom Hanks", "label": "person", "properties": 
{"name": "Tom Hanks", "age": 67}},
+            {"id": "movie:Forrest Gump", "label": "movie", "properties": 
{"title": "Forrest Gump", "year": 1994}},
+        ]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {"role": "Forrest Gump"},
+                "outV": "person:Tom Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        self.assertEqual(vertices[0]["id"], "1:Tom Hanks")
+        self.assertEqual(vertices[1]["id"], "2:Forrest Gump")
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addEdge,
+            "acted_in",
+            "1:Tom Hanks",
+            "2:Forrest Gump",
+            {"role": "Forrest Gump"},
+        )
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def 
test_load_into_graph_maps_multiple_primary_keys_to_created_vertex_ids(self, 
mock_handle_graph_creation):
+        mock_handle_graph_creation.side_effect = [
+            MagicMock(id="1:Tom!Hanks"),
+            MagicMock(id="2:Forrest Gump"),
+            MagicMock(id="edge_id"),
+        ]
+        schema = {
+            "propertykeys": [
+                {"name": "first", "data_type": "TEXT", "cardinality": 
"SINGLE"},
+                {"name": "last", "data_type": "TEXT", "cardinality": "SINGLE"},
+                {"name": "title", "data_type": "TEXT", "cardinality": 
"SINGLE"},
+            ],
+            "vertexlabels": [
+                {
+                    "id": 1,
+                    "name": "person",
+                    "properties": ["first", "last"],
+                    "primary_keys": ["first", "last"],
+                    "nullable_keys": [],
+                    "id_strategy": "PRIMARY_KEY",
+                },
+                {
+                    "id": 2,
+                    "name": "movie",
+                    "properties": ["title"],
+                    "primary_keys": ["title"],
+                    "nullable_keys": [],
+                    "id_strategy": "PRIMARY_KEY",
+                },
+            ],
+            "edgelabels": [{"name": "acted_in", "properties": [], 
"source_label": "person", "target_label": "movie"}],
+        }
+        vertices = [
+            {"label": "person", "properties": {"first": "Tom", "last": 
"Hanks"}},
+            {"label": "movie", "properties": {"title": "Forrest Gump"}},
+        ]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {},
+                "outV": "person:Tom!Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        self.commit2graph.load_into_graph(vertices, edges, schema)
+
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addEdge,
+            "acted_in",
+            "1:Tom!Hanks",
+            "2:Forrest Gump",
+            {},
+        )
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def test_load_into_graph_uses_explicit_customize_string_ids(self, 
mock_handle_graph_creation):
+        """Test custom string ids are passed to HugeGraph when schema requires 
them."""
+        mock_handle_graph_creation.side_effect = [
+            MagicMock(id="Tom Hanks"),
+            MagicMock(id="Forrest Gump"),
+            MagicMock(id="edge_id"),
+        ]
+        schema = {
+            "propertykeys": [
+                {"name": "name", "data_type": "TEXT", "cardinality": "SINGLE"},
+                {"name": "title", "data_type": "TEXT", "cardinality": 
"SINGLE"},
+            ],
+            "vertexlabels": [
+                {
+                    "id": 7,
+                    "name": "person",
+                    "id_strategy": "CUSTOMIZE_STRING",
+                    "primary_keys": ["name"],
+                    "properties": ["name"],
+                    "nullable_keys": [],
+                },
+                {
+                    "id": 8,
+                    "name": "movie",
+                    "id_strategy": "CUSTOMIZE_STRING",
+                    "primary_keys": ["title"],
+                    "properties": ["title"],
+                    "nullable_keys": [],
+                },
+            ],
+            "edgelabels": [{"name": "acted_in", "properties": [], 
"source_label": "person", "target_label": "movie"}],
+        }
+        vertices = [
+            {"id": "Tom Hanks", "label": "person", "properties": {"name": "Tom 
Hanks"}},
+            {"id": "Forrest Gump", "label": "movie", "properties": {"title": 
"Forrest Gump"}},
+        ]
+        edges = [{"label": "acted_in", "properties": {}, "outV": "Tom Hanks", 
"inV": "Forrest Gump"}]
+
+        self.commit2graph.load_into_graph(vertices, edges, schema)
+
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addVertex,
+            "person",
+            {"name": "Tom Hanks"},
+            id="Tom Hanks",
+        )
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addVertex,
+            "movie",
+            {"title": "Forrest Gump"},
+            id="Forrest Gump",
+        )
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addEdge,
+            "acted_in",
+            "Tom Hanks",
+            "Forrest Gump",
+            {},
+        )
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def 
test_load_into_graph_accepts_normalized_extraction_without_item_type(self, 
mock_handle_graph_creation):
+        """Test normalized LLM output without type fields can be committed."""
+        mock_handle_graph_creation.side_effect = [
+            MagicMock(id="1:Tom Hanks"),
+            MagicMock(id="2:Forrest Gump"),
+            MagicMock(id="edge_id"),
+        ]
+        llm_output = """{
+            "vertices": [
+            {
+                "id": "person:Tom Hanks",
+                "label": "person",
+                "properties": {
+                    "name": "Tom Hanks",
+                    "age": 67
+                }
+            },
+            {
+                "id": "movie:Forrest Gump",
+                "label": "movie",
+                "properties": {
+                    "title": "Forrest Gump",
+                    "year": 1994
+                }
+            }
+            ],
+            "edges": [
+            {
+                "label": "acted_in",
+                "outV": "person:Tom Hanks",
+                "outVLabel": "person",
+                "inV": "movie:Forrest Gump",
+                "inVLabel": "movie",
+                "properties": {
+                    "role": "Forrest Gump"
+                }
+            }
+            ]
+        }"""
+
+        items = 
PropertyGraphExtract(llm=MagicMock())._extract_and_filter_label(self.schema, 
llm_output)
+        vertices = [item for item in items if item["type"] == "vertex"]
+        edges = [item for item in items if item["type"] == "edge"]
+        self.assertEqual(edges[0]["outV"], "1:Tom Hanks")
+        self.assertEqual(edges[0]["inV"], "2:Forrest Gump")
+
+        self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        mock_handle_graph_creation.assert_any_call(
+            self.commit2graph.client.graph().addEdge,
+            "acted_in",
+            "1:Tom Hanks",
+            "2:Forrest Gump",
+            {"role": "Forrest Gump"},
+        )
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def 
test_load_into_graph_raises_explicit_error_when_vertex_creation_fails(self, 
mock_handle_graph_creation):
+        """Test failed vertex creation is reported before edge creation."""
+        mock_handle_graph_creation.return_value = None
+
+        vertices = [{"label": "person", "properties": {"name": "Tom Hanks", 
"age": 67}}]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {"role": "Forrest Gump"},
+                "outV": "person:Tom Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        with self.assertRaisesRegex(ValueError, "Failed to create vertex"):
+            self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        mock_handle_graph_creation.assert_called_once()
+
+    
@patch("hugegraph_llm.operators.hugegraph_op.commit_to_hugegraph.Commit2Graph._handle_graph_creation")
+    def test_load_into_graph_with_data_type_validation_failure(self, 
mock_handle_graph_creation):
+        """Test load_into_graph method with data type validation failure."""
+        mock_handle_graph_creation.return_value = MagicMock(id="vertex_id")
+
+        vertices = [
+            {"label": "person", "properties": {"name": "Tom Hanks", "age": 
"67"}},
+            {"label": "movie", "properties": {"title": "Forrest Gump", "year": 
"1994"}},
+        ]
+        edges = [
+            {
+                "label": "acted_in",
+                "properties": {"role": "Forrest Gump"},
+                "outV": "person:Tom Hanks",
+                "inV": "movie:Forrest Gump",
+            }
+        ]
+
+        self.commit2graph.load_into_graph(vertices, edges, self.schema)
+
+        self.assertEqual(mock_handle_graph_creation.call_count, 1)
diff --git a/hugegraph-llm/src/tests/operators/llm_op/test_gremlin_generate.py 
b/hugegraph-llm/src/tests/operators/llm_op/test_gremlin_generate.py
index 9a6e7659..a9963952 100644
--- a/hugegraph-llm/src/tests/operators/llm_op/test_gremlin_generate.py
+++ b/hugegraph-llm/src/tests/operators/llm_op/test_gremlin_generate.py
@@ -22,10 +22,10 @@ import unittest
 from unittest.mock import AsyncMock, MagicMock, patch
 
 import pytest
+from fixtures.fake_llm import FakeLLM
 
 from hugegraph_llm.models.llms.base import BaseLLM
 from hugegraph_llm.operators.llm_op.gremlin_generate import 
GremlinGenerateSynthesize
-from tests.fixtures.fake_llm import FakeLLM
 
 pytestmark = pytest.mark.contract
 
diff --git a/hugegraph-llm/src/tests/operators/llm_op/test_keyword_extract.py 
b/hugegraph-llm/src/tests/operators/llm_op/test_keyword_extract.py
index 6dbeb01e..d5c4f286 100644
--- a/hugegraph-llm/src/tests/operators/llm_op/test_keyword_extract.py
+++ b/hugegraph-llm/src/tests/operators/llm_op/test_keyword_extract.py
@@ -21,10 +21,10 @@ import unittest
 from unittest.mock import MagicMock, patch
 
 import pytest
+from fixtures.fake_llm import FakeLLM
 
 from hugegraph_llm.models.llms.base import BaseLLM
 from hugegraph_llm.operators.llm_op.keyword_extract import KeywordExtract
-from tests.fixtures.fake_llm import FakeLLM
 
 pytestmark = pytest.mark.contract
 
diff --git 
a/hugegraph-llm/src/tests/operators/llm_op/test_property_graph_extract.py 
b/hugegraph-llm/src/tests/operators/llm_op/test_property_graph_extract.py
index 3b4bd25e..9a5b09f7 100644
--- a/hugegraph-llm/src/tests/operators/llm_op/test_property_graph_extract.py
+++ b/hugegraph-llm/src/tests/operators/llm_op/test_property_graph_extract.py
@@ -22,6 +22,7 @@ import unittest
 from unittest.mock import MagicMock, patch
 
 import pytest
+from fixtures.fake_llm import FakeLLM
 
 from hugegraph_llm.models.llms.base import BaseLLM
 from hugegraph_llm.operators.llm_op.property_graph_extract import (
@@ -30,7 +31,6 @@ from hugegraph_llm.operators.llm_op.property_graph_extract 
import (
     generate_extract_property_graph_prompt,
     split_text,
 )
-from tests.fixtures.fake_llm import FakeLLM
 
 pytestmark = pytest.mark.contract
 
diff --git a/hugegraph-python-client/src/tests/api/test_gremlin.py 
b/hugegraph-python-client/src/tests/api/test_gremlin.py
index 3dbd9473..1eb4a21d 100644
--- a/hugegraph-python-client/src/tests/api/test_gremlin.py
+++ b/hugegraph-python-client/src/tests/api/test_gremlin.py
@@ -139,7 +139,7 @@ class TestGremlinSetupBehavior(unittest.TestCase):
 
 
 def test_gremlin_error_surface_is_explicit(client_utils):
-    with pytest.raises(Exception) as exc_info:
+    with pytest.raises(NotFoundError) as exc_info:
         client_utils.gremlin.exec("g.V2()")
 
     message = str(exc_info.value)
diff --git a/hugegraph-python-client/src/tests/api/test_schema.py 
b/hugegraph-python-client/src/tests/api/test_schema.py
index 55e1ecce..fa1343bb 100644
--- a/hugegraph-python-client/src/tests/api/test_schema.py
+++ b/hugegraph-python-client/src/tests/api/test_schema.py
@@ -16,6 +16,7 @@
 # under the License.
 
 import unittest
+from contextlib import suppress
 
 import pytest
 
@@ -87,21 +88,36 @@ class TestSchemaManager(unittest.TestCase):
 
 def test_schema_create_and_fetch_property_vertex_edge_index(client_utils):
     schema = client_utils.schema
-    schema.propertyKey("quality_name").asText().ifNotExist().create()
-    schema.propertyKey("quality_score").asInt().ifNotExist().create()
-    schema.vertexLabel("quality_person").properties("quality_name", 
"quality_score").primaryKeys(
-        "quality_name"
-    ).ifNotExist().create()
-    
schema.edgeLabel("quality_knows").sourceLabel("quality_person").targetLabel("quality_person").ifNotExist().create()
-    
schema.indexLabel("quality_person_by_score").onV("quality_person").by("quality_score").range().ifNotExist().create()
-
-    full_schema = schema.getSchema()
-
-    assert "propertykeys" in full_schema
-    assert "vertexlabels" in full_schema
-    assert "edgelabels" in full_schema
-    assert "indexlabels" in full_schema
-    assert schema.getPropertyKey("quality_name").name == "quality_name"
-    assert schema.getVertexLabel("quality_person").name == "quality_person"
-    assert schema.getEdgeLabel("quality_knows").name == "quality_knows"
-    assert schema.getIndexLabel("quality_person_by_score").name == 
"quality_person_by_score"
+    try:
+        schema.propertyKey("quality_name").asText().ifNotExist().create()
+        schema.propertyKey("quality_score").asInt().ifNotExist().create()
+        schema.vertexLabel("quality_person").properties("quality_name", 
"quality_score").primaryKeys(
+            "quality_name"
+        ).ifNotExist().create()
+        
schema.edgeLabel("quality_knows").sourceLabel("quality_person").targetLabel(
+            "quality_person"
+        ).ifNotExist().create()
+        schema.indexLabel("quality_person_by_score").onV("quality_person").by(
+            "quality_score"
+        ).range().ifNotExist().create()
+
+        full_schema = schema.getSchema()
+
+        assert "propertykeys" in full_schema
+        assert "vertexlabels" in full_schema
+        assert "edgelabels" in full_schema
+        assert "indexlabels" in full_schema
+        assert schema.getPropertyKey("quality_name").name == "quality_name"
+        assert schema.getVertexLabel("quality_person").name == "quality_person"
+        assert schema.getEdgeLabel("quality_knows").name == "quality_knows"
+        assert schema.getIndexLabel("quality_person_by_score").name == 
"quality_person_by_score"
+    finally:
+        for remove in (
+            lambda: schema.indexLabel("quality_person_by_score").remove(),
+            lambda: schema.edgeLabel("quality_knows").remove(),
+            lambda: schema.vertexLabel("quality_person").remove(),
+            lambda: schema.propertyKey("quality_score").remove(),
+            lambda: schema.propertyKey("quality_name").remove(),
+        ):
+            with suppress(Exception):
+                remove()

Reply via email to