Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
3 changes: 2 additions & 1 deletion openviking/server/routers/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
8 changes: 7 additions & 1 deletion openviking/server/routers/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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."""
Expand Down
11 changes: 10 additions & 1 deletion openviking/service/debug_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
3 changes: 1 addition & 2 deletions openviking/storage/collection_schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion openviking_cli/client/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 =============
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions tests/eval/test_ragas_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down
4 changes: 2 additions & 2 deletions tests/eval/test_ragas_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
14 changes: 5 additions & 9 deletions tests/integration/test_full_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions tests/misc/test_mkdir.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down
7 changes: 2 additions & 5 deletions tests/misc/test_tree_builder_dedup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand All @@ -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."""
Expand Down Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions tests/misc/test_vikingdb_observer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions tests/server/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion tests/server/test_api_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 15 additions & 12 deletions tests/session/test_session_commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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, (
Expand Down
3 changes: 1 addition & 2 deletions tests/test_edge_cases.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion tests/vectordb/benchmark_stress.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]

Expand Down
2 changes: 1 addition & 1 deletion tests/vectordb/test_collection_large_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 2 additions & 2 deletions tests/vectordb/test_crash_recovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 1 addition & 1 deletion tests/vectordb/test_data_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions tests/vectordb/test_filter_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
6 changes: 3 additions & 3 deletions tests/vectordb/test_openviking_vectordb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/vectordb/test_project_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Loading
Loading