541 lines
No EOL
19 KiB
Python
541 lines
No EOL
19 KiB
Python
"""
|
|
Tests for KB metadata and types functionality.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
from app.core.kb_metadata import (
|
|
generate_tags,
|
|
format_timestamp,
|
|
get_documents_metadata,
|
|
get_collection_stats,
|
|
get_kb_type_badge,
|
|
get_kb_type_summary
|
|
)
|
|
from app.core.kb_types import (
|
|
KBType,
|
|
KBMetadata,
|
|
KBTypeInfo,
|
|
get_kb_type_info,
|
|
validate_kb_type,
|
|
get_kb_type_display_name,
|
|
get_all_kb_types,
|
|
get_kb_type_choices
|
|
)
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestKBMetadata:
|
|
"""Test KBMetadata class."""
|
|
|
|
def test_kb_metadata_creation(self):
|
|
"""Test KBMetadata creation with default values."""
|
|
metadata = KBMetadata()
|
|
|
|
assert metadata.kb_type == KBType.GENERIC
|
|
assert metadata.description == ""
|
|
assert isinstance(metadata.created_at, str)
|
|
assert metadata.tags == []
|
|
|
|
def test_kb_metadata_creation_with_values(self):
|
|
"""Test KBMetadata creation with custom values."""
|
|
test_date = "2023-01-01T00:00:00"
|
|
metadata = KBMetadata(
|
|
kb_type=KBType.CODE,
|
|
description="Test code KB",
|
|
created_at=test_date,
|
|
tags=["python", "javascript"]
|
|
)
|
|
|
|
assert metadata.kb_type == KBType.CODE
|
|
assert metadata.description == "Test code KB"
|
|
assert metadata.created_at == test_date
|
|
assert metadata.tags == ["python", "javascript"]
|
|
|
|
def test_kb_metadata_to_dict(self):
|
|
"""Test KBMetadata to_dict conversion."""
|
|
metadata = KBMetadata(
|
|
kb_type=KBType.DOCUMENTATION,
|
|
description="Test docs",
|
|
tags=["api", "guide"]
|
|
)
|
|
|
|
result = metadata.to_dict()
|
|
|
|
assert result["kb_type"] == "documentation"
|
|
assert result["description"] == "Test docs"
|
|
assert result["created_at"] == metadata.created_at
|
|
assert result["tags"] == "api,guide" # Comma-separated
|
|
|
|
def test_kb_metadata_from_dict(self):
|
|
"""Test KBMetadata from_dict creation."""
|
|
data = {
|
|
"kb_type": "agent-os",
|
|
"description": "Test Agent OS",
|
|
"created_at": "2023-01-01T00:00:00",
|
|
"tags": "standard,workflow,spec"
|
|
}
|
|
|
|
metadata = KBMetadata.from_dict(data)
|
|
|
|
assert metadata.kb_type == KBType.AGENT_OS
|
|
assert metadata.description == "Test Agent OS"
|
|
assert metadata.created_at == "2023-01-01T00:00:00"
|
|
assert metadata.tags == ["standard", "workflow", "spec"]
|
|
|
|
def test_kb_metadata_from_dict_empty_tags(self):
|
|
"""Test KBMetadata from_dict with empty tags."""
|
|
data = {
|
|
"kb_type": "generic",
|
|
"description": "Test generic",
|
|
"created_at": "2023-01-01T00:00:00",
|
|
"tags": ""
|
|
}
|
|
|
|
metadata = KBMetadata.from_dict(data)
|
|
|
|
assert metadata.tags == []
|
|
|
|
def test_kb_metadata_from_dict_no_tags(self):
|
|
"""Test KBMetadata from_dict with no tags field."""
|
|
data = {
|
|
"kb_type": "generic",
|
|
"description": "Test generic",
|
|
"created_at": "2023-01-01T00:00:00"
|
|
}
|
|
|
|
metadata = KBMetadata.from_dict(data)
|
|
|
|
assert metadata.tags == []
|
|
|
|
def test_kb_metadata_from_dict_invalid_type(self):
|
|
"""Test KBMetadata from_dict with invalid type."""
|
|
data = {
|
|
"kb_type": "invalid_type",
|
|
"description": "Test invalid",
|
|
"created_at": "2023-01-01T00:00:00",
|
|
"tags": ""
|
|
}
|
|
|
|
metadata = KBMetadata.from_dict(data)
|
|
|
|
# Should fall back to GENERIC
|
|
assert metadata.kb_type == KBType.GENERIC
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestKBType:
|
|
"""Test KBType enum."""
|
|
|
|
def test_kb_type_values(self):
|
|
"""Test KBType enum values."""
|
|
assert KBType.GENERIC.value == "generic"
|
|
assert KBType.CODE.value == "code"
|
|
assert KBType.DOCUMENTATION.value == "documentation"
|
|
assert KBType.AGENT_OS.value == "agent-os"
|
|
|
|
def test_kb_type_string_representation(self):
|
|
"""Test KBType string representation."""
|
|
assert str(KBType.GENERIC) == "generic"
|
|
assert str(KBType.CODE) == "code"
|
|
assert str(KBType.DOCUMENTATION) == "documentation"
|
|
assert str(KBType.AGENT_OS) == "agent-os"
|
|
|
|
def test_kb_type_comparison(self):
|
|
"""Test KBType comparison."""
|
|
assert KBType.GENERIC == "generic"
|
|
assert KBType.GENERIC != KBType.CODE
|
|
assert KBType.GENERIC == KBType.GENERIC
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestKBTypeFunctions:
|
|
"""Test KB type utility functions."""
|
|
|
|
def test_validate_kb_type_valid(self):
|
|
"""Test validate_kb_type with valid types."""
|
|
assert validate_kb_type("generic") == KBType.GENERIC
|
|
assert validate_kb_type("code") == KBType.CODE
|
|
assert validate_kb_type("documentation") == KBType.DOCUMENTATION
|
|
assert validate_kb_type("agent-os") == KBType.AGENT_OS
|
|
|
|
def test_validate_kb_type_invalid(self):
|
|
"""Test validate_kb_type with invalid types."""
|
|
assert validate_kb_type("invalid") is None
|
|
assert validate_kb_type("") is None
|
|
assert validate_kb_type("unknown") is None
|
|
|
|
def test_get_kb_type_info(self):
|
|
"""Test get_kb_type_info function."""
|
|
# Test each type
|
|
for kb_type in KBType:
|
|
info = get_kb_type_info(kb_type)
|
|
|
|
assert isinstance(info, KBTypeInfo)
|
|
assert info.icon is not None
|
|
assert info.color is not None
|
|
assert info.name is not None
|
|
assert info.description is not None
|
|
assert isinstance(info.use_cases, list)
|
|
|
|
def test_get_kb_type_info_default(self):
|
|
"""Test get_kb_type_info with invalid type (should return default)."""
|
|
info = get_kb_type_info("invalid_type")
|
|
|
|
# Should return GENERIC info
|
|
assert info.icon == "📦"
|
|
assert info.name == "Generic"
|
|
|
|
def test_get_kb_type_display_name(self):
|
|
"""Test get_kb_type_display_name function."""
|
|
generic_display = get_kb_type_display_name(KBType.GENERIC)
|
|
assert "📦" in generic_display
|
|
assert "Generic" in generic_display
|
|
|
|
code_display = get_kb_type_display_name(KBType.CODE)
|
|
assert "💻" in code_display
|
|
assert "Code Repository" in code_display
|
|
|
|
def test_get_all_kb_types(self):
|
|
"""Test get_all_kb_types function."""
|
|
all_types = get_all_kb_types()
|
|
|
|
assert isinstance(all_types, list)
|
|
assert len(all_types) == 4
|
|
assert KBType.GENERIC in all_types
|
|
assert KBType.CODE in all_types
|
|
assert KBType.DOCUMENTATION in all_types
|
|
assert KBType.AGENT_OS in all_types
|
|
|
|
def test_get_kb_type_choices(self):
|
|
"""Test get_kb_type_choices function."""
|
|
choices = get_kb_type_choices()
|
|
|
|
assert isinstance(choices, dict)
|
|
assert len(choices) == 4
|
|
|
|
# Check that all display names are keys
|
|
display_names = list(choices.keys())
|
|
assert any("📦 Generic" in name for name in display_names)
|
|
assert any("💻 Code Repository" in name for name in display_names)
|
|
assert any("📚 Documentation" in name for name in display_names)
|
|
assert any("🤖 Agent OS Profile" in name for name in display_names)
|
|
|
|
# Check that all types are values
|
|
type_values = list(choices.values())
|
|
assert KBType.GENERIC in type_values
|
|
assert KBType.CODE in type_values
|
|
assert KBType.DOCUMENTATION in type_values
|
|
assert KBType.AGENT_OS in type_values
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestKBMetadataFunctions:
|
|
"""Test KB metadata utility functions."""
|
|
|
|
def test_generate_tags(self):
|
|
"""Test generate_tags function."""
|
|
# Test known file types
|
|
assert generate_tags(".py") == ["Code", "Python"]
|
|
assert generate_tags(".js") == ["Code", "JavaScript"]
|
|
assert generate_tags(".jsx") == ["Code", "React"]
|
|
assert generate_tags(".ts") == ["Code", "TypeScript"]
|
|
assert generate_tags(".tsx") == ["Code", "React", "TypeScript"]
|
|
assert generate_tags(".go") == ["Code", "Go"]
|
|
assert generate_tags(".rs") == ["Code", "Rust"]
|
|
assert generate_tags(".java") == ["Code", "Java"]
|
|
assert generate_tags(".cpp") == ["Code", "C++"]
|
|
assert generate_tags(".c") == ["Code", "C"]
|
|
assert generate_tags(".h") == ["Code", "Header"]
|
|
assert generate_tags(".md") == ["Document", "Markdown"]
|
|
assert generate_tags(".txt") == ["Document", "Text"]
|
|
assert generate_tags(".pdf") == ["Document", "PDF"]
|
|
assert generate_tags(".json") == ["Config", "JSON"]
|
|
assert generate_tags(".yaml") == ["Config", "YAML"]
|
|
assert generate_tags(".yml") == ["Config", "YAML"]
|
|
|
|
# Test unknown file type
|
|
assert generate_tags(".xyz") == ["Document", "Unknown"]
|
|
assert generate_tags("") == ["Document", "Unknown"]
|
|
|
|
def test_generate_tags_case_insensitive(self):
|
|
"""Test generate_tags function is case insensitive."""
|
|
assert generate_tags(".PY") == ["Code", "Python"]
|
|
assert generate_tags(".JS") == ["Code", "JavaScript"]
|
|
assert generate_tags(".MD") == ["Document", "Markdown"]
|
|
|
|
def test_format_timestamp_valid(self):
|
|
"""Test format_timestamp with valid timestamp."""
|
|
timestamp = "2023-01-15T14:30:00"
|
|
formatted = format_timestamp(timestamp)
|
|
|
|
assert formatted == "Updated: 01/15/2023"
|
|
|
|
def test_format_timestamp_invalid(self):
|
|
"""Test format_timestamp with invalid timestamp."""
|
|
formatted = format_timestamp("invalid-timestamp")
|
|
|
|
assert formatted == "Updated: Unknown"
|
|
|
|
def test_format_timestamp_none(self):
|
|
"""Test format_timestamp with None."""
|
|
formatted = format_timestamp(None)
|
|
|
|
assert formatted == "Updated: Unknown"
|
|
|
|
def test_get_kb_type_badge(self):
|
|
"""Test get_kb_type_badge function."""
|
|
badge = get_kb_type_badge(KBType.GENERIC)
|
|
|
|
assert "📦" in badge
|
|
assert "Generic" in badge
|
|
assert "background:" in badge
|
|
assert "color:" in badge
|
|
assert "padding:" in badge
|
|
assert "border-radius:" in badge
|
|
assert "display:" in badge
|
|
|
|
def test_get_kb_type_badge_all_types(self):
|
|
"""Test get_kb_type_badge for all types."""
|
|
for kb_type in KBType:
|
|
badge = get_kb_type_badge(kb_type)
|
|
|
|
# Should contain icon and name
|
|
info = get_kb_type_info(kb_type)
|
|
assert info.icon in badge
|
|
assert info.name in badge
|
|
# Should contain color
|
|
assert info.color in badge
|
|
|
|
def test_get_kb_type_summary(self):
|
|
"""Test get_kb_type_summary function."""
|
|
# Mock database manager
|
|
with patch('app.core.kb_metadata.get_sqlite_manager') as mock_get_db:
|
|
mock_db = MagicMock()
|
|
mock_db.list_collections.return_value = [
|
|
{"metadata": {"kb_type": "generic"}},
|
|
{"metadata": {"kb_type": "code"}},
|
|
{"metadata": {"kb_type": "code"}},
|
|
{"metadata": {"kb_type": "documentation"}},
|
|
{"metadata": {"kb_type": "agent-os"}}
|
|
]
|
|
mock_get_db.return_value = mock_db
|
|
|
|
summary = get_kb_type_summary()
|
|
|
|
assert summary["generic"] == 1
|
|
assert summary["code"] == 2
|
|
assert summary["documentation"] == 1
|
|
assert summary["agent-os"] == 1
|
|
|
|
def test_get_kb_type_summary_with_unknown_types(self):
|
|
"""Test get_kb_type_summary with unknown types."""
|
|
# Mock database manager
|
|
with patch('app.core.kb_metadata.get_sqlite_manager') as mock_get_db:
|
|
mock_db = MagicMock()
|
|
mock_db.list_collections.return_value = [
|
|
{"metadata": {"kb_type": "generic"}},
|
|
{"metadata": {"kb_type": "unknown_type"}},
|
|
{"metadata": {"kb_type": "another_unknown"}}
|
|
]
|
|
mock_get_db.return_value = mock_db
|
|
|
|
summary = get_kb_type_summary()
|
|
|
|
# Unknown types should be counted as generic
|
|
assert summary["generic"] == 3 # 1 real + 2 unknown
|
|
assert summary["code"] == 0
|
|
assert summary["documentation"] == 0
|
|
assert summary["agent-os"] == 0
|
|
|
|
def test_get_kb_type_summary_empty(self):
|
|
"""Test get_kb_type_summary with no collections."""
|
|
# Mock database manager
|
|
with patch('app.core.kb_metadata.get_sqlite_manager') as mock_get_db:
|
|
mock_db = MagicMock()
|
|
mock_db.list_collections.return_value = []
|
|
mock_get_db.return_value = mock_db
|
|
|
|
summary = get_kb_type_summary()
|
|
|
|
# All counts should be 0
|
|
for kb_type in KBType:
|
|
assert summary[kb_type.value] == 0
|
|
|
|
|
|
@pytest.mark.integration
|
|
class TestKBMetadataIntegration:
|
|
"""Integration tests for KB metadata functionality."""
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_documents_metadata_real_data(self, mock_get_db, sample_kb, sample_documents):
|
|
"""Test get_documents_metadata with real data."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
mock_db.get_documents_by_metadata.return_value = [
|
|
{
|
|
"metadata": {
|
|
"filename": "test_0.txt",
|
|
"file_type": ".txt",
|
|
"upload_date": "2023-01-01T00:00:00",
|
|
"chunk_index": 0
|
|
}
|
|
},
|
|
{
|
|
"metadata": {
|
|
"filename": "test_0.txt",
|
|
"file_type": ".txt",
|
|
"upload_date": "2023-01-01T00:00:00",
|
|
"chunk_index": 1
|
|
}
|
|
},
|
|
{
|
|
"metadata": {
|
|
"filename": "test_1.txt",
|
|
"file_type": ".txt",
|
|
"upload_date": "2023-01-02T00:00:00",
|
|
"chunk_index": 0
|
|
}
|
|
}
|
|
]
|
|
mock_get_db.return_value = mock_db
|
|
|
|
metadata = get_documents_metadata(sample_kb["name"])
|
|
|
|
# Should group chunks by filename
|
|
assert len(metadata) == 2 # 2 unique filenames
|
|
|
|
# Check first document
|
|
doc0 = next(doc for doc in metadata if doc["filename"] == "test_0.txt")
|
|
assert doc0["file_type"] == ".txt"
|
|
assert doc0["tags"] == ["Document", "Text"]
|
|
assert doc0["upload_date"] == "2023-01-01T00:00:00"
|
|
assert doc0["formatted_date"] == "Updated: 01/01/2023"
|
|
assert doc0["chunk_count"] == 2 # 2 chunks for this file
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_collection_stats_real_data(self, mock_get_db, sample_kb, sample_documents):
|
|
"""Test get_collection_stats with real data."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
mock_db.get_collection_metadata.return_value = {
|
|
"kb_type": "code",
|
|
"description": "Test code KB",
|
|
"created_at": "2023-01-01T00:00:00"
|
|
}
|
|
mock_db.get_documents_by_metadata.return_value = [
|
|
{"metadata": {"filename": "test_0.txt", "upload_date": "2023-01-01T00:00:00"}},
|
|
{"metadata": {"filename": "test_0.txt", "upload_date": "2023-01-01T00:00:00"}},
|
|
{"metadata": {"filename": "test_1.txt", "upload_date": "2023-01-02T00:00:00"}}
|
|
]
|
|
mock_get_db.return_value = mock_db
|
|
|
|
stats = get_collection_stats(sample_kb["name"])
|
|
|
|
assert stats["total_documents"] == 2 # 2 unique filenames
|
|
assert stats["total_chunks"] == 3 # 3 total chunks
|
|
assert stats["last_updated"] == "2023-01-02T00:00:00" # Latest upload
|
|
assert stats["kb_type"] == "code"
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_collection_stats_nonexistent_kb(self, mock_get_db):
|
|
"""Test get_collection_stats with non-existent KB."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = False
|
|
mock_get_db.return_value = mock_db
|
|
|
|
stats = get_collection_stats("nonexistent_kb")
|
|
|
|
# Should return default stats
|
|
assert stats["total_documents"] == 0
|
|
assert stats["total_chunks"] == 0
|
|
assert stats["last_updated"] is None
|
|
assert stats["kb_type"] == "generic"
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_collection_stats_empty_kb(self, mock_get_db, sample_kb):
|
|
"""Test get_collection_stats with empty KB."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
mock_db.get_collection_metadata.return_value = {
|
|
"kb_type": "generic",
|
|
"description": "Empty KB",
|
|
"created_at": "2023-01-01T00:00:00"
|
|
}
|
|
mock_db.get_documents_by_metadata.return_value = []
|
|
mock_get_db.return_value = mock_db
|
|
|
|
stats = get_collection_stats(sample_kb["name"])
|
|
|
|
assert stats["total_documents"] == 0
|
|
assert stats["total_chunks"] == 0
|
|
assert stats["last_updated"] is None
|
|
assert stats["kb_type"] == "generic"
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_documents_metadata_sorted_by_date(self, mock_get_db, sample_kb):
|
|
"""Test that get_documents_metadata sorts by upload date."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
|
|
# Create documents with different dates
|
|
docs = []
|
|
for i in range(5):
|
|
upload_date = f"2023-01-{i:02d}T00:00:00"
|
|
docs.append({
|
|
"metadata": {
|
|
"filename": f"test_{i}.txt",
|
|
"upload_date": upload_date,
|
|
"chunk_index": 0
|
|
}
|
|
})
|
|
|
|
# Shuffle to test sorting
|
|
import random
|
|
random.shuffle(docs)
|
|
|
|
mock_db.get_documents_by_metadata.return_value = docs
|
|
mock_get_db.return_value = mock_db
|
|
|
|
metadata = get_documents_metadata(sample_kb["name"])
|
|
|
|
# Should be sorted by date (newest first)
|
|
dates = [doc["upload_date"] for doc in metadata]
|
|
assert dates == sorted(dates, reverse=True)
|
|
|
|
# First should be newest (01-05)
|
|
assert metadata[0]["filename"] == "test_4.txt"
|
|
# Last should be oldest (01-01)
|
|
assert metadata[-1]["filename"] == "test_0.txt"
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_documents_metadata_error_handling(self, mock_get_db):
|
|
"""Test get_documents_metadata error handling."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
mock_db.get_documents_by_metadata.side_effect = Exception("Database error")
|
|
mock_get_db.return_value = mock_db
|
|
|
|
metadata = get_documents_metadata("test_kb")
|
|
|
|
# Should return empty list on error
|
|
assert metadata == []
|
|
|
|
@patch('app.core.kb_metadata.get_sqlite_manager')
|
|
def test_get_collection_stats_error_handling(self, mock_get_db):
|
|
"""Test get_collection_stats error handling."""
|
|
mock_db = MagicMock()
|
|
mock_db.collection_exists.return_value = True
|
|
mock_db.get_collection_metadata.side_effect = Exception("Database error")
|
|
mock_db.get_documents_by_metadata.side_effect = Exception("Database error")
|
|
mock_get_db.return_value = mock_db
|
|
|
|
stats = get_collection_stats("test_kb")
|
|
|
|
# Should return default stats on error
|
|
assert stats["total_documents"] == 0
|
|
assert stats["total_chunks"] == 0
|
|
assert stats["last_updated"] is None
|
|
assert stats["kb_type"] == "generic" |