Skip to main content
Glama

COA Goldfish MCP

by anortham
ServiceTestBase.csโ€ข11.7 kB
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; using Microsoft.EntityFrameworkCore; using Microsoft.Data.Sqlite; using NUnit.Framework; using COA.Goldfish.McpServer.Services.Storage; using COA.Goldfish.McpServer.Services; using COA.Goldfish.McpServer.Tools; namespace COA.Goldfish.IntegrationTests; /// <summary> /// Base class for service-based testing that eliminates MCP server process spawning. /// Provides direct access to all MCP tools and services with proper database isolation. /// </summary> public abstract class ServiceTestBase { protected IServiceProvider? _serviceProvider; protected IServiceScope? _serviceScope; protected GoldfishDbContext? _context; // Direct access to all MCP tools - no process spawning required protected CheckpointTool? _checkpointTool; protected TodoTool? _todoTool; protected PlanTool? _planTool; protected StandupTool? _standupTool; protected ChronicleTool? _chronicleTool; protected WorkspaceTool? _workspaceTool; protected RecallTool? _recallTool; protected SearchTool? _searchTool; // Access to core services protected WorkspaceService? _workspaceService; protected COA.Goldfish.McpServer.Services.ISearchService? _searchService; protected COA.Goldfish.McpServer.Services.IPathResolutionService? _pathResolutionService; protected string _testWorkspacePath = ""; protected string _testWorkspaceId = ""; [SetUp] public virtual async Task SetUp() { await SetUpServicesAsync(); await InitializeTestWorkspaceAsync(); } /// <summary> /// Sets up the complete service dependency injection container with all MCP tools. /// This replaces MCP server process spawning with direct service calls. /// </summary> protected virtual async Task SetUpServicesAsync() { // Create isolated test database _context = CreateTestContext(); await TestDbContextFactory.InitializeAsync(_context, false); // Set up dependency injection container var services = new ServiceCollection(); // Add logging services.AddLogging(builder => builder.AddConsole() .SetMinimumLevel(LogLevel.Information)); // Register specific loggers for tools services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); // Add configuration (required by PathResolutionService) var configuration = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary<string, string?> { ["Goldfish:PrimaryWorkspace"] = _testWorkspacePath }) .Build(); services.AddSingleton<IConfiguration>(configuration); // Add database context factory for proper threading isolation // Each scope gets its own context, but sharing the same database connection for SQLite services.AddScoped<GoldfishDbContext>(_ => { if (_context!.Database.IsSqlite()) { // For SQLite, create new context with same connection var connection = _context.Database.GetDbConnection(); var options = new DbContextOptionsBuilder<GoldfishDbContext>() .UseSqlite(connection) .EnableSensitiveDataLogging() .EnableDetailedErrors() .Options; return new GoldfishDbContext(options); } else { // For InMemory EF, return the shared instance return _context; } }); // Add core services (matching main application) services.AddScoped<COA.Goldfish.McpServer.Services.Storage.IStorageService, COA.Goldfish.McpServer.Services.Storage.StorageService>(); services.AddScoped<DatabaseInitializer>(); services.AddScoped<COA.Goldfish.McpServer.Services.IPathResolutionService, PathResolutionService>(); services.AddScoped<WorkspaceService>(); services.AddScoped<COA.Goldfish.McpServer.Services.ISearchService, SearchService>(); // Add all MCP tools (matching main application) services.AddScoped<CheckpointTool>(); services.AddScoped<TodoTool>(); services.AddScoped<PlanTool>(); services.AddScoped<StandupTool>(); services.AddScoped<ChronicleTool>(); services.AddScoped<WorkspaceTool>(); services.AddScoped<RecallTool>(); services.AddScoped<SearchTool>(); // Build service provider _serviceProvider = services.BuildServiceProvider(); // Create a scope for scoped services - store for proper disposal _serviceScope = _serviceProvider.CreateScope(); var scopedProvider = _serviceScope.ServiceProvider; // Get tool instances from scoped provider _checkpointTool = scopedProvider.GetRequiredService<CheckpointTool>(); _todoTool = scopedProvider.GetRequiredService<TodoTool>(); _planTool = scopedProvider.GetRequiredService<PlanTool>(); _standupTool = scopedProvider.GetRequiredService<StandupTool>(); _chronicleTool = scopedProvider.GetRequiredService<ChronicleTool>(); _workspaceTool = scopedProvider.GetRequiredService<WorkspaceTool>(); _recallTool = scopedProvider.GetRequiredService<RecallTool>(); _searchTool = scopedProvider.GetRequiredService<SearchTool>(); // Get service instances from scoped provider _workspaceService = scopedProvider.GetRequiredService<WorkspaceService>(); _searchService = scopedProvider.GetRequiredService<COA.Goldfish.McpServer.Services.ISearchService>(); _pathResolutionService = scopedProvider.GetRequiredService<COA.Goldfish.McpServer.Services.IPathResolutionService>(); } /// <summary> /// Creates the appropriate test database context. /// Override this method to customize database type for specific test scenarios. /// </summary> protected virtual GoldfishDbContext CreateTestContext() { // Default to in-memory EF database for speed // Override in derived classes for SQLite FTS5 testing return TestDbContextFactory.CreateInMemoryContext(); } /// <summary> /// Initializes an isolated test workspace for this test session. /// </summary> protected virtual async Task InitializeTestWorkspaceAsync() { _testWorkspaceId = Guid.NewGuid().ToString(); _testWorkspacePath = Path.Combine(Path.GetTempPath(), $"goldfish_test_workspace_{_testWorkspaceId}"); // Create workspace directory if using file-based testing Directory.CreateDirectory(_testWorkspacePath); // Add workspace to database var workspace = new COA.Goldfish.McpServer.Models.WorkspaceState { WorkspaceId = _testWorkspaceId, LastActivity = DateTime.UtcNow }; _context!.WorkspaceStates.Add(workspace); await _context.SaveChangesAsync(); } [TearDown] public virtual async Task TearDown() { await CleanupServicesAsync(); await CleanupTestWorkspaceAsync(); } /// <summary> /// Properly disposes all services and cleans up database resources. /// Critical for preventing file locks and memory leaks. /// </summary> protected virtual async Task CleanupServicesAsync() { // Dispose service scope first _serviceScope?.Dispose(); _serviceScope = null; // Dispose service provider if (_serviceProvider is IDisposable disposableServiceProvider) { disposableServiceProvider.Dispose(); } _serviceProvider = null; // Clean up database context if (_context != null) { await TestDbContextFactory.CleanupAsync(_context); _context = null; } // Clear SQLite connection pools to prevent file locks SqliteConnection.ClearAllPools(); // Force garbage collection to ensure all connections are disposed GC.Collect(); GC.WaitForPendingFinalizers(); } /// <summary> /// Cleans up the test workspace directory and associated resources. /// </summary> protected virtual async Task CleanupTestWorkspaceAsync() { await Task.Delay(1); // Ensure async context try { if (Directory.Exists(_testWorkspacePath)) { Directory.Delete(_testWorkspacePath, recursive: true); } } catch (Exception) { // Ignore cleanup errors - they don't affect test results } } /// <summary> /// Helper method to create realistic checkpoint data for testing. /// </summary> protected virtual COA.Goldfish.McpServer.Models.CheckpointParameters CreateCheckpointRequest( string description = "Test checkpoint", List<string>? highlights = null, List<string>? activeFiles = null) { return new COA.Goldfish.McpServer.Models.CheckpointParameters { Action = "save", Description = description, Highlights = highlights ?? new List<string> { "Test highlight" }, ActiveFiles = activeFiles ?? new List<string> { "TestFile.cs" }, WorkContext = "Test work context", Workspace = _testWorkspaceId, Global = false }; } /// <summary> /// Helper method to create todo list request for testing. /// </summary> protected virtual COA.Goldfish.McpServer.Models.TodoParameters CreateTodoRequest( string title = "Test Todo List", List<string>? items = null) { return new COA.Goldfish.McpServer.Models.TodoParameters { Action = "create", Title = title, Items = items ?? new List<string> { "Test todo item" }, Workspace = _testWorkspaceId }; } /// <summary> /// Helper method to create plan request for testing. /// </summary> protected virtual COA.Goldfish.McpServer.Models.PlanParameters CreatePlanRequest( string title = "Test Plan", string description = "Test plan description", List<string>? items = null) { return new COA.Goldfish.McpServer.Models.PlanParameters { Action = "save", Title = title, Description = description, Items = items ?? new List<string> { "Test plan item" }, Category = "test", Priority = "medium", Workspace = _testWorkspaceId }; } /// <summary> /// Helper method to verify that a database operation succeeded. /// </summary> protected virtual async Task<T?> GetEntityByIdAsync<T>(string id) where T : class { return await _context!.Set<T>().FindAsync(id); } /// <summary> /// Helper method to count entities of a specific type in the test database. /// </summary> protected virtual async Task<int> CountEntitiesAsync<T>() where T : class { return await Task.FromResult(_context!.Set<T>().Count()); } /// <summary> /// Helper method to verify workspace isolation. /// </summary> protected virtual async Task<bool> IsWorkspaceIsolatedAsync() { var workspaces = _context!.WorkspaceStates.ToList(); return await Task.FromResult(workspaces.All(w => w.WorkspaceId == _testWorkspaceId)); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/anortham/coa-goldfish-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server