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()