bkmr 7.0.0

A Unified CLI Tool for Bookmark, Snippet, and Knowledge Management
Documentation
// src/util/test_service_container.rs
use crate::application::services::action_service::{ActionService, ActionServiceImpl};
use crate::application::services::bookmark_service::BookmarkService;
use crate::application::services::interpolation_service::InterpolationService;
use crate::application::services::tag_service::TagService;
use crate::application::services::template_service::TemplateService;
use crate::application::{
    BookmarkServiceImpl, InterpolationServiceImpl, TagServiceImpl, TemplateServiceImpl,
};
use crate::domain::action::BookmarkAction;
use crate::domain::action_resolver::{ActionResolver, SystemTagActionResolver};
use crate::domain::embedding::Embedder;
use crate::domain::services::clipboard::ClipboardService;
use crate::infrastructure::clipboard::ClipboardServiceImpl;
use crate::domain::repositories::vector_repository::VectorRepository;
use crate::infrastructure::embeddings::DummyEmbedding;
use crate::infrastructure::interpolation::minijinja_engine::{MiniJinjaEngine, SafeShellExecutor};
use crate::infrastructure::repositories::file_import_repository::FileImportRepository;
use crate::infrastructure::repositories::null_vector_repository::NullVectorRepository;
use crate::infrastructure::repositories::sqlite::repository::SqliteBookmarkRepository;
use crate::lsp::backend::BkmrConfig;
use crate::lsp::services::snippet_service::LspSnippetService;
use crate::util::testing::EnvGuard;
use std::sync::Arc;

/// Test service container - single source of truth for test service creation
/// Provides the same services as production ServiceContainer but with test-specific configuration
pub struct TestServiceContainer {
    // Environment isolation
    pub _env_guard: EnvGuard,

    // Core services
    pub bookmark_repository: Arc<SqliteBookmarkRepository>,
    pub embedder: Arc<dyn Embedder>,
    pub vector_repository: Arc<dyn VectorRepository>,
    pub bookmark_service: Arc<dyn BookmarkService>,
    pub tag_service: Arc<dyn TagService>,
    pub action_service: Arc<dyn ActionService>,

    // Utility services
    pub clipboard_service: Arc<dyn ClipboardService>,
    pub interpolation_service: Arc<dyn InterpolationService>,
    pub template_service: Arc<dyn TemplateService>,
}

impl TestServiceContainer {
    /// Create all test services with explicit dependency injection
    /// Equivalent to ServiceContainer::new but with test-specific setup
    pub fn new() -> Self {
        // Initialize test environment (includes logging setup)
        let env_guard = EnvGuard::new();

        // Create test database with proper migrations (shared test database)
        let bookmark_repository = Self::create_shared_test_db();
        let embedder = Self::create_test_embedder();
        let vector_repository: Arc<dyn VectorRepository> = Arc::new(NullVectorRepository);
        let clipboard_service = Arc::new(ClipboardServiceImpl::new());
        let interpolation_service = Self::create_interpolation_service();
        let template_service = Self::create_template_service();

        // Application services with explicit DI
        let bookmark_service = Arc::new(BookmarkServiceImpl::new(
            bookmark_repository.clone(),
            embedder.clone(),
            vector_repository.clone(),
            Arc::new(FileImportRepository::new()),
        ));

        let tag_service = Arc::new(TagServiceImpl::new(bookmark_repository.clone()));

        let action_service = Self::create_test_action_service(
            &bookmark_repository,
            &interpolation_service,
            &(clipboard_service.clone() as Arc<dyn ClipboardService>),
            &embedder,
        );

        Self {
            _env_guard: env_guard,
            bookmark_repository,
            embedder,
            vector_repository,
            bookmark_service,
            tag_service,
            action_service,
            clipboard_service,
            interpolation_service,
            template_service,
        }
    }

    /// Create shared test database (same as original setup_test_db approach)
    fn create_shared_test_db() -> Arc<SqliteBookmarkRepository> {
        // Use the same approach as the original setup_test_db function
        Arc::new(crate::util::testing::setup_test_db())
    }

    /// Create test embedder (always dummy for consistent test behavior)
    fn create_test_embedder() -> Arc<dyn Embedder> {
        Arc::new(DummyEmbedding)
    }

    /// Create interpolation service for test (same logic as production ServiceContainer)
    fn create_interpolation_service() -> Arc<dyn InterpolationService> {
        let shell_executor = Arc::new(SafeShellExecutor::new());
        let interpolation_engine = Arc::new(MiniJinjaEngine::new(shell_executor));
        Arc::new(InterpolationServiceImpl::new(interpolation_engine))
    }

    /// Create template service (simplified for tests)
    fn create_template_service() -> Arc<dyn TemplateService> {
        Arc::new(TemplateServiceImpl::new())
    }

    /// Create action service for tests with test configuration
    fn create_test_action_service(
        repository: &Arc<SqliteBookmarkRepository>,
        interpolation_service: &Arc<dyn InterpolationService>,
        clipboard_service: &Arc<dyn ClipboardService>,
        embedder: &Arc<dyn Embedder>,
    ) -> Arc<dyn ActionService> {
        let resolver = Self::create_test_action_resolver(
            repository,
            interpolation_service,
            clipboard_service,
            embedder,
        );
        Arc::new(ActionServiceImpl::new(resolver, repository.clone()))
    }

    /// Create action resolver for tests with test-friendly settings
    fn create_test_action_resolver(
        repository: &Arc<SqliteBookmarkRepository>,
        interpolation_service: &Arc<dyn InterpolationService>,
        clipboard_service: &Arc<dyn ClipboardService>,
        embedder: &Arc<dyn Embedder>,
    ) -> Arc<dyn ActionResolver> {
        // Create all actions with explicit dependencies - using test-friendly settings
        let uri_action: Box<dyn BookmarkAction> = Box::new(
            crate::application::actions::UriAction::new(interpolation_service.clone()),
        );

        let snippet_action: Box<dyn BookmarkAction> =
            Box::new(crate::application::actions::SnippetAction::new(
                clipboard_service.clone(),
                interpolation_service.clone(),
            ));

        let text_action: Box<dyn BookmarkAction> =
            Box::new(crate::application::actions::TextAction::new(
                clipboard_service.clone(),
                interpolation_service.clone(),
            ));

        // Non-interactive shell action for tests
        let shell_action: Box<dyn BookmarkAction> =
            Box::new(crate::application::actions::ShellAction::new(
                interpolation_service.clone(),
                false, // non-interactive for tests
            ));

        let markdown_action: Box<dyn BookmarkAction> = Box::new(
            crate::application::actions::MarkdownAction::new_with_repository(
                repository.clone(),
                Arc::new(crate::infrastructure::repositories::null_vector_repository::NullVectorRepository),
                embedder.clone(),
            ),
        );

        let env_action: Box<dyn BookmarkAction> = Box::new(
            crate::application::actions::EnvAction::new(interpolation_service.clone()),
        );

        let default_action: Box<dyn BookmarkAction> = Box::new(
            crate::application::actions::DefaultAction::new(interpolation_service.clone()),
        );

        Arc::new(SystemTagActionResolver::new(
            uri_action,
            snippet_action,
            text_action,
            shell_action,
            markdown_action,
            env_action,
            default_action,
        ))
    }

    /// Create LSP services bundle for integration testing
    /// Returns pre-configured services ready for LSP backend testing
    pub fn create_lsp_services(&self) -> LspServiceBundle {
        let snippet_service = Arc::new(LspSnippetService::with_services(
            self.bookmark_service.clone(),
            self.interpolation_service.clone(),
        ));

        let completion_service =
            crate::lsp::services::completion_service::CompletionService::with_config(
                snippet_service.clone(),
                BkmrConfig::default(),
            );

        let document_service = crate::lsp::services::document_service::DocumentService::new();

        let command_service = crate::lsp::services::command_service::CommandService::with_service(
            self.bookmark_service.clone(),
        );

        LspServiceBundle {
            snippet_service,
            completion_service,
            document_service,
            command_service,
        }
    }
}

/// Bundle of LSP services for integration testing
/// Provides all LSP services pre-configured and ready to use
pub struct LspServiceBundle {
    pub snippet_service: Arc<LspSnippetService>,
    pub completion_service: crate::lsp::services::completion_service::CompletionService,
    pub document_service: crate::lsp::services::document_service::DocumentService,
    pub command_service: crate::lsp::services::command_service::CommandService,
}

impl Default for TestServiceContainer {
    fn default() -> Self {
        Self::new()
    }
}

impl std::fmt::Debug for TestServiceContainer {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("TestServiceContainer")
            .field("bookmark_repository", &"Arc<SqliteBookmarkRepository>")
            .field("embedder", &"Arc<dyn Embedder>")
            .field("vector_repository", &"Arc<dyn VectorRepository>")
            .field("bookmark_service", &"Arc<dyn BookmarkService>")
            .field("tag_service", &"Arc<dyn TagService>")
            .field("action_service", &"Arc<dyn ActionService>")
            .field("clipboard_service", &"Arc<dyn ClipboardService>")
            .field("interpolation_service", &"Arc<dyn InterpolationService>")
            .field("template_service", &"Arc<dyn TemplateService>")
            .finish()
    }
}