diff --git a/.gitignore b/.gitignore index 90296c16..f620ee84 100644 --- a/.gitignore +++ b/.gitignore @@ -157,15 +157,6 @@ dmypy.json cython_debug/ openviking/bin/ test_scripts/ -test_large_scale_collection/ - -# Test-generated directories -.tmp_*/ -db_test_*/ -test_recall_collection/ -test_db_*/ -test_project_root/ -benchmark_stress_db/ examples/data/ openviking/_version.py specs/ diff --git a/openviking/server/routers/filesystem.py b/openviking/server/routers/filesystem.py index f34f1632..ba710c74 100644 --- a/openviking/server/routers/filesystem.py +++ b/openviking/server/routers/filesystem.py @@ -74,7 +74,8 @@ async def stat( result = await service.fs.stat(uri, ctx=_ctx) return Response(status="ok", result=result) except AGFSClientError as e: - if "no such file or directory" in str(e).lower(): + err_msg = str(e).lower() + if "not found" in err_msg or "no such file or directory" in err_msg: raise NotFoundError(uri, "file") raise diff --git a/openviking/server/routers/resources.py b/openviking/server/routers/resources.py index 2daaaa48..b9ef3aa7 100644 --- a/openviking/server/routers/resources.py +++ b/openviking/server/routers/resources.py @@ -8,7 +8,7 @@ from typing import Any, Optional from fastapi import APIRouter, Depends, File, UploadFile -from pydantic import BaseModel +from pydantic import BaseModel, model_validator from openviking.server.auth import get_request_context from openviking.server.dependencies import get_service @@ -35,6 +35,12 @@ class AddResourceRequest(BaseModel): exclude: Optional[str] = None directly_upload_media: bool = True + @model_validator(mode="after") + def check_path_or_temp_path(self): + if not self.path and not self.temp_path: + raise ValueError("Either 'path' or 'temp_path' must be provided") + return self + class AddSkillRequest(BaseModel): """Request model for add_skill.""" diff --git a/openviking/service/debug_service.py b/openviking/service/debug_service.py index 15056429..2e8253f9 100644 --- a/openviking/service/debug_service.py +++ b/openviking/service/debug_service.py @@ -81,7 +81,16 @@ def _dependencies_ready(self) -> bool: @property def queue(self) -> ComponentStatus: """Get queue status.""" - observer = QueueObserver(get_queue_manager()) + try: + qm = get_queue_manager() + except Exception: + return ComponentStatus( + name="queue", + is_healthy=False, + has_errors=True, + status="Not initialized", + ) + observer = QueueObserver(qm) return ComponentStatus( name="queue", is_healthy=observer.is_healthy(), diff --git a/openviking/storage/collection_schemas.py b/openviking/storage/collection_schemas.py index 0cff8725..ba09d49e 100644 --- a/openviking/storage/collection_schemas.py +++ b/openviking/storage/collection_schemas.py @@ -204,8 +204,7 @@ async def on_dequeue(self, data: Optional[Dict[str, Any]]) -> Optional[Dict[str, uri = inserted_data.get("uri") if uri: account_id = inserted_data.get("account_id", "default") - owner_space = inserted_data.get("owner_space", "") - id_seed = f"{account_id}:{owner_space}:{uri}" + id_seed = f"{account_id}:{uri}" inserted_data["id"] = hashlib.md5(id_seed.encode("utf-8")).hexdigest() record_id = await self._vikingdb.upsert(inserted_data) diff --git a/openviking_cli/client/http.py b/openviking_cli/client/http.py index e3cad61e..20c7c2e9 100644 --- a/openviking_cli/client/http.py +++ b/openviking_cli/client/http.py @@ -183,7 +183,10 @@ async def initialize(self) -> None: async def close(self) -> None: """Close the HTTP client.""" if self._http: - await self._http.aclose() + try: + await self._http.aclose() + except RuntimeError: + pass self._http = None # ============= Internal Helpers ============= diff --git a/tests/conftest.py b/tests/conftest.py index 8773ff8b..9ace5753 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -14,8 +14,8 @@ from openviking import AsyncOpenViking # Test data root directory -TEST_ROOT = Path(__file__).parent -TEST_TMP_DIR = TEST_ROOT / ".tmp" +PROJECT_ROOT = Path(__file__).parent.parent +TEST_TMP_DIR = PROJECT_ROOT / "test_data" / "tmp" @pytest.fixture(scope="session") diff --git a/tests/eval/test_ragas_basic.py b/tests/eval/test_ragas_basic.py index edd3b382..348a93a4 100644 --- a/tests/eval/test_ragas_basic.py +++ b/tests/eval/test_ragas_basic.py @@ -30,9 +30,9 @@ def test_generator_initialization(): def test_pipeline_initialization(): - pipeline = RAGQueryPipeline(config_path="./test.conf", data_path="./test_data") + pipeline = RAGQueryPipeline(config_path="./test.conf", data_path="./test_data/test_ragas") assert pipeline.config_path == "./test.conf" - assert pipeline.data_path == "./test_data" + assert pipeline.data_path == "./test_data/test_ragas" assert pipeline._client is None diff --git a/tests/eval/test_ragas_validation.py b/tests/eval/test_ragas_validation.py index d4a321b0..272aed1a 100644 --- a/tests/eval/test_ragas_validation.py +++ b/tests/eval/test_ragas_validation.py @@ -105,10 +105,10 @@ def test_pipeline_initialization(): from openviking.eval.ragas.pipeline import RAGQueryPipeline - pipeline = RAGQueryPipeline(config_path="./test.conf", data_path="./test_data") + pipeline = RAGQueryPipeline(config_path="./test.conf", data_path="./test_data/test_ragas") assert pipeline.config_path == "./test.conf" - assert pipeline.data_path == "./test_data" + assert pipeline.data_path == "./test_data/test_ragas" assert pipeline._client is None print(" ✅ RAGQueryPipeline initialized successfully") diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 82a06e43..fceae36b 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -22,8 +22,8 @@ from openviking.service.core import OpenVikingService from openviking_cli.session.user_id import UserIdentifier -TEST_ROOT = Path(__file__).parent -TEST_TMP_DIR = TEST_ROOT / ".tmp_integration" +PROJECT_ROOT = Path(__file__).parent.parent.parent +TEST_TMP_DIR = PROJECT_ROOT / "test_data" / "tmp_integration" @pytest.fixture(scope="session") diff --git a/tests/integration/test_full_workflow.py b/tests/integration/test_full_workflow.py index daf8c77c..3f86b559 100644 --- a/tests/integration/test_full_workflow.py +++ b/tests/integration/test_full_workflow.py @@ -67,15 +67,11 @@ async def test_add_search_read_workflow( # 3. Read searched resource if search_result.resources: - if search_result.resources[0].is_leaf: - content = await client.read(search_result.resources[0].uri) - assert len(content) > 0 - else: - res = await client.tree(search_result.resources[0].uri) - for data in res: - if not data["isDir"]: - content = await client.read(data["uri"]) - assert len(content) > 0 + res = await client.tree(search_result.resources[0].uri) + for data in res: + if not data["isDir"]: + content = await client.read(data["uri"]) + assert len(content) > 0 class TestSessionWorkflow: diff --git a/tests/misc/test_mkdir.py b/tests/misc/test_mkdir.py index 5c559971..ed008705 100644 --- a/tests/misc/test_mkdir.py +++ b/tests/misc/test_mkdir.py @@ -3,6 +3,7 @@ # SPDX-License-Identifier: Apache-2.0 """Tests for VikingFS.mkdir() — verifies the target directory is actually created.""" +import contextvars import os import sys from unittest.mock import AsyncMock, MagicMock @@ -22,6 +23,7 @@ def _make_viking_fs(): fs.query_embedder = None fs.vector_store = None fs._uri_prefix = "viking://" + fs._bound_ctx = contextvars.ContextVar("vikingfs_bound_ctx", default=None) return fs diff --git a/tests/misc/test_tree_builder_dedup.py b/tests/misc/test_tree_builder_dedup.py index 52f15de8..0610c60c 100644 --- a/tests/misc/test_tree_builder_dedup.py +++ b/tests/misc/test_tree_builder_dedup.py @@ -16,7 +16,7 @@ def _make_viking_fs_mock(existing_uris: set[str]): """Create a mock VikingFS whose stat() raises for non-existing URIs.""" fs = MagicMock() - async def _stat(uri): + async def _stat(uri, **kwargs): if uri in existing_uris: return {"name": uri.split("/")[-1], "isDir": True} raise FileNotFoundError(f"Not found: {uri}") @@ -26,7 +26,6 @@ async def _stat(uri): class TestResolveUniqueUri: - @pytest.mark.asyncio async def test_no_conflict(self): """When the URI is free, return it unchanged.""" @@ -85,9 +84,7 @@ async def test_max_attempts_exceeded(self): with patch("openviking.parse.tree_builder.get_viking_fs", return_value=fs): with pytest.raises(FileExistsError, match="Cannot resolve unique name"): - await builder._resolve_unique_uri( - "viking://resources/report", max_attempts=5 - ) + await builder._resolve_unique_uri("viking://resources/report", max_attempts=5) @pytest.mark.asyncio async def test_gap_in_sequence(self): diff --git a/tests/misc/test_vikingdb_observer.py b/tests/misc/test_vikingdb_observer.py index 35318fc5..310d01b6 100644 --- a/tests/misc/test_vikingdb_observer.py +++ b/tests/misc/test_vikingdb_observer.py @@ -15,7 +15,7 @@ async def test_vikingdb_observer(): print("=== Test VikingDBObserver ===") # Create client - client = ov.AsyncOpenViking(path="./test_data") + client = ov.AsyncOpenViking(path="./test_data/test_vikingdb_observer") try: # Initialize client @@ -81,7 +81,7 @@ def test_sync_client(): """Test sync client""" print("\n=== Test sync client ===") - client = ov.OpenViking(path="./test_data") + client = ov.OpenViking(path="./test_data/test_vikingdb_observer") try: # Initialize diff --git a/tests/server/conftest.py b/tests/server/conftest.py index 9f6d6279..4ed3586e 100644 --- a/tests/server/conftest.py +++ b/tests/server/conftest.py @@ -25,8 +25,8 @@ # Paths # --------------------------------------------------------------------------- -TEST_ROOT = Path(__file__).parent -TEST_TMP_DIR = TEST_ROOT / ".tmp_server" +PROJECT_ROOT = Path(__file__).parent.parent.parent +TEST_TMP_DIR = PROJECT_ROOT / "test_data" / "tmp_server" # --------------------------------------------------------------------------- # Sample data diff --git a/tests/server/test_api_content.py b/tests/server/test_api_content.py index 76e90363..e7f2970b 100644 --- a/tests/server/test_api_content.py +++ b/tests/server/test_api_content.py @@ -15,7 +15,8 @@ async def test_read_content(client_with_resource): # Find a file (non-directory) to read file_uri = None if children: - file_uri = uri.rstrip("/") + "/" + children[0] if isinstance(children[0], str) else None + # ls(simple=True) returns full URIs, use directly + file_uri = children[0] if isinstance(children[0], str) else None if file_uri is None: file_uri = uri diff --git a/tests/session/test_session_commit.py b/tests/session/test_session_commit.py index 04d73b36..86066b84 100644 --- a/tests/session/test_session_commit.py +++ b/tests/session/test_session_commit.py @@ -76,9 +76,7 @@ async def test_commit_with_usage_records(self, client: AsyncOpenViking): assert result.get("status") == "committed" assert "active_count_updated" in result - async def test_active_count_incremented_after_commit( - self, client_with_resource_sync: tuple - ): + async def test_active_count_incremented_after_commit(self, client_with_resource_sync: tuple): """Regression test: active_count must actually increment after commit. Previously _update_active_counts() had three bugs: @@ -92,18 +90,19 @@ async def test_active_count_incremented_after_commit( has child records triggers a 'Duplicate records found' error and returns None — leaving active_count un-updated even after fixes 1 and 2. - Fix: derive the record id as md5(uri) (the canonical formula used in - collection_schemas.py) and call storage.get(ids=[id]) for an exact lookup. + Fix: use storage.filter() to look up the record by URI and read + its stored id, then call storage.update() with that id. """ - import hashlib - client, uri = client_with_resource_sync vikingdb = client._client.service.vikingdb_manager - # Use storage.get() for exact ID-based lookup (avoids fetch_by_uri subtree issue) - record_id = hashlib.md5(uri.encode("utf-8")).hexdigest() - records_before = await vikingdb.get("context", [record_id]) - assert records_before, f"Resource not found by id for URI: {uri}" + # Look up the record by URI + records_before = await vikingdb.get_context_by_uri( + account_id="default", + uri=uri, + limit=1, + ) + assert records_before, f"Resource not found for URI: {uri}" count_before = records_before[0].get("active_count") or 0 # Mark as used and commit @@ -116,7 +115,11 @@ async def test_active_count_incremented_after_commit( assert result.get("active_count_updated") == 1 # Verify the count actually changed in storage - records_after = await vikingdb.get("context", [record_id]) + records_after = await vikingdb.get_context_by_uri( + account_id="default", + uri=uri, + limit=1, + ) assert records_after, f"Record disappeared after commit for URI: {uri}" count_after = records_after[0].get("active_count") or 0 assert count_after == count_before + 1, ( diff --git a/tests/test_edge_cases.py b/tests/test_edge_cases.py index 9504b436..f6bd2702 100644 --- a/tests/test_edge_cases.py +++ b/tests/test_edge_cases.py @@ -29,12 +29,11 @@ is_text_file, upload_directory, ) -from openviking.storage.vikingdb_interface import VikingDBInterface from openviking.utils.compression import CompressManager from openviking_cli.utils.uri import VikingURI -class MockVikingDB(VikingDBInterface): +class MockVikingDB: """Mock vector database for testing.""" def __init__(self): diff --git a/tests/vectordb/benchmark_stress.py b/tests/vectordb/benchmark_stress.py index 48b4c5fb..d9aa33e7 100644 --- a/tests/vectordb/benchmark_stress.py +++ b/tests/vectordb/benchmark_stress.py @@ -10,7 +10,7 @@ # --- Configuration --- DEFAULT_DIM = 128 -DEFAULT_DB_PATH = "./benchmark_stress_db" +DEFAULT_DB_PATH = "./test_data/benchmark_stress_db" CATEGORIES = ["news", "sports", "finance", "tech", "entertainment"] TAGS = ["hot", "new", "archived", "premium", "public"] diff --git a/tests/vectordb/test_collection_large_scale.py b/tests/vectordb/test_collection_large_scale.py index 85cac5c3..867358c6 100644 --- a/tests/vectordb/test_collection_large_scale.py +++ b/tests/vectordb/test_collection_large_scale.py @@ -14,7 +14,7 @@ from openviking.storage.vectordb.collection.local_collection import get_or_create_local_collection # Test data path -TEST_DB_PATH = "./test_large_scale_collection/" +TEST_DB_PATH = "./test_data/test_large_scale_collection/" class TestLargeScaleScenarios(unittest.TestCase): diff --git a/tests/vectordb/test_crash_recovery.py b/tests/vectordb/test_crash_recovery.py index 7a1922d2..2e734332 100644 --- a/tests/vectordb/test_crash_recovery.py +++ b/tests/vectordb/test_crash_recovery.py @@ -9,8 +9,8 @@ from openviking.storage.vectordb.collection.local_collection import get_or_create_local_collection -DB_PATH_CRASH = "./test_db_crash_recovery" -DB_PATH_ROBUST = "./test_db_robust_crash" +DB_PATH_CRASH = "./test_data/test_db_crash_recovery" +DB_PATH_ROBUST = "./test_data/test_db_robust_crash" def worker_write_and_crash(path, start_id, count, event_ready): diff --git a/tests/vectordb/test_data_processor.py b/tests/vectordb/test_data_processor.py index e52ba7a9..c13f37bf 100644 --- a/tests/vectordb/test_data_processor.py +++ b/tests/vectordb/test_data_processor.py @@ -9,7 +9,7 @@ from openviking.storage.vectordb.utils.data_processor import DataProcessor -DB_PATH = "./db_test_data_processor/" +DB_PATH = "./test_data/db_test_data_processor/" def clean_dir(path: str) -> None: diff --git a/tests/vectordb/test_filter_ops.py b/tests/vectordb/test_filter_ops.py index b84e4640..ccc30538 100644 --- a/tests/vectordb/test_filter_ops.py +++ b/tests/vectordb/test_filter_ops.py @@ -9,10 +9,10 @@ from openviking.storage.vectordb.collection.local_collection import get_or_create_local_collection -db_path_basic = "./db_test_filters_basic/" -db_path_complex = "./db_test_filters_complex/" -db_path_lifecycle = "./db_test_filters_lifecycle/" -db_path_scale = "./db_test_filters_scale/" +db_path_basic = "./test_data/db_test_filters_basic/" +db_path_complex = "./test_data/db_test_filters_complex/" +db_path_lifecycle = "./test_data/db_test_filters_lifecycle/" +db_path_scale = "./test_data/db_test_filters_scale/" def clean_dir(path): diff --git a/tests/vectordb/test_openviking_vectordb.py b/tests/vectordb/test_openviking_vectordb.py index e061673a..805950d3 100644 --- a/tests/vectordb/test_openviking_vectordb.py +++ b/tests/vectordb/test_openviking_vectordb.py @@ -6,7 +6,7 @@ from openviking.storage.vectordb.collection.local_collection import get_or_create_local_collection -TEST_DB_PATH = "./db_test_openviking_vectordb/" +TEST_DB_PATH = "./test_data/db_test_openviking_vectordb/" def clean_dir(path: str) -> None: @@ -475,14 +475,14 @@ class TestOpenVikingVectorDBIP(TestOpenVikingVectorDB): def setUp(self): super().setUp() global TEST_DB_PATH - TEST_DB_PATH = "./db_test_openviking_vectordb_ip/" + TEST_DB_PATH = "./test_data/db_test_openviking_vectordb_ip/" clean_dir(TEST_DB_PATH) def tearDown(self): super().tearDown() global TEST_DB_PATH clean_dir(TEST_DB_PATH) - TEST_DB_PATH = "./db_test_openviking_vectordb/" # Reset + TEST_DB_PATH = "./test_data/db_test_openviking_vectordb/" # Reset def _create_collection(self): vector_dim = 1024 diff --git a/tests/vectordb/test_project_group.py b/tests/vectordb/test_project_group.py index 093956e6..ddc8f19b 100644 --- a/tests/vectordb/test_project_group.py +++ b/tests/vectordb/test_project_group.py @@ -6,7 +6,7 @@ from openviking.storage.vectordb.project.project_group import get_or_create_project_group -TEST_PROJECT_ROOT = "./test_project_root" +TEST_PROJECT_ROOT = "./test_data/test_project_root" class TestProjectGroup(unittest.TestCase): diff --git a/tests/vectordb/test_recall.py b/tests/vectordb/test_recall.py index 07fb01f2..fbc8de0a 100644 --- a/tests/vectordb/test_recall.py +++ b/tests/vectordb/test_recall.py @@ -8,7 +8,7 @@ from openviking.storage.vectordb.collection.local_collection import get_or_create_local_collection # Test data path -TEST_DB_PATH = "./test_recall_collection/" +TEST_DB_PATH = "./test_data/test_recall_collection/" def calculate_l2_distance(v1: List[float], v2: List[float]) -> float: