MarketAlly.AIPlugin.Extensions/MarketAlly.AIPlugin.Learnin.../Integration/IntegrationTests.cs

453 lines
18 KiB
C#
Executable File

using MarketAlly.AIPlugin.Learning;
using MarketAlly.AIPlugin.Learning.Configuration;
using MarketAlly.AIPlugin.Learning.Models;
using MarketAlly.AIPlugin.Learning.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using RefactorIQ.Services.Interfaces;
namespace MarketAlly.AIPlugin.Learning.Tests.Integration
{
[TestClass]
public class IntegrationTests
{
private IServiceProvider? _serviceProvider;
private IConfiguration? _configuration;
[TestInitialize]
public void Setup()
{
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["Learning:Git:BranchPrefix"] = "test-ai-refactoring",
["Learning:Git:CommitterName"] = "Test AI Learning System",
["Learning:Git:CommitterEmail"] = "test-ai@learning.system",
["Learning:LearningModes:Conservative:MaxIterations"] = "5",
["Learning:LearningModes:Moderate:MaxIterations"] = "10",
["Learning:LearningModes:Aggressive:MaxIterations"] = "20",
["Learning:AI:EnableSemanticSearch"] = "false",
["Learning:AI:MaxSearchResults"] = "5",
["Learning:AI:MinSimilarityScore"] = "0.8",
["Learning:AI:MaxContextTokens"] = "4000",
["Learning:Security:MaxFileSizeBytes"] = "5242880", // 5MB
["Learning:Security:MaxPathLength"] = "200"
});
_configuration = configBuilder.Build();
var services = new ServiceCollection();
services.AddSingleton(_configuration);
services.Configure<LearningConfiguration>(_configuration.GetSection(LearningConfiguration.SectionName));
services.AddLogging(builder => builder.AddConsole().SetMinimumLevel(LogLevel.Warning));
// Register services
services.AddSingleton<ISecurityService, SecurityService>(provider =>
new SecurityService(
provider.GetRequiredService<IOptions<LearningConfiguration>>(),
provider.GetRequiredService<ILogger<SecurityService>>(),
Path.GetTempPath()));
services.AddSingleton<IRefactorIQClient>(sp => Mock.Of<IRefactorIQClient>());
services.AddSingleton<ILLMContextService, LLMContextService>();
services.AddSingleton<IUnifiedContextService, UnifiedContextService>();
_serviceProvider = services.BuildServiceProvider();
}
[TestCleanup]
public void Cleanup()
{
if (_serviceProvider is IDisposable disposable)
{
disposable.Dispose();
}
}
[TestMethod]
public void ServiceProvider_AllServicesRegistered_CanResolveServices()
{
// Act & Assert
Assert.IsNotNull(_serviceProvider?.GetService<ISecurityService>());
Assert.IsNotNull(_serviceProvider?.GetService<ILLMContextService>());
Assert.IsNotNull(_serviceProvider?.GetService<IUnifiedContextService>());
Assert.IsNotNull(_serviceProvider?.GetService<IConfiguration>());
Assert.IsNotNull(_serviceProvider?.GetService<IOptions<LearningConfiguration>>());
}
[TestMethod]
public void Configuration_LoadedCorrectly_HasExpectedValues()
{
// Arrange
var options = _serviceProvider?.GetRequiredService<IOptions<LearningConfiguration>>();
// Act
var config = options?.Value;
// Assert
Assert.IsNotNull(config);
Assert.AreEqual("test-ai-refactoring", config.Git.BranchPrefix);
Assert.AreEqual("Test AI Learning System", config.Git.CommitterName);
Assert.AreEqual("test-ai@learning.system", config.Git.CommitterEmail);
Assert.AreEqual(5, config.LearningModes.Conservative.MaxIterations);
Assert.AreEqual(10, config.LearningModes.Moderate.MaxIterations);
Assert.AreEqual(20, config.LearningModes.Aggressive.MaxIterations);
Assert.AreEqual(4000, config.AI.MaxContextTokens);
Assert.AreEqual(5242880, config.Security.MaxFileSizeBytes);
}
[TestMethod]
public async Task UnifiedContextService_IntegrationWithLLMContextService_WorksTogether()
{
// Arrange
var unifiedContextService = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var query = "integration test query";
// Act
var result = await unifiedContextService!.PrepareFullContextAsync(query);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(query, result.Query);
Assert.IsNotNull(result.CurrentCodeAnalysis);
Assert.IsNotNull(result.HistoricalInsights);
Assert.IsNotNull(result.RelatedDecisions);
Assert.IsTrue(result.GeneratedAt <= DateTime.UtcNow);
}
[TestMethod]
public async Task SecurityService_IntegrationWithConfiguration_ValidatesCorrectly()
{
// Arrange
var securityService = _serviceProvider?.GetRequiredService<ISecurityService>();
var config = _serviceProvider?.GetRequiredService<IOptions<LearningConfiguration>>()?.Value;
// Act
var validationResult = securityService!.ValidateConfiguration(config!);
var pathSafetyResult = securityService.IsPathSafe(Path.Combine(Path.GetTempPath(), "test.cs"));
// Assert
Assert.IsTrue(validationResult.IsValid);
Assert.IsTrue(pathSafetyResult);
}
[TestMethod]
public async Task LLMContextService_WithConfiguredSettings_RespectsTokenLimits()
{
// Arrange
var llmContextService = _serviceProvider?.GetRequiredService<ILLMContextService>();
var config = _serviceProvider?.GetRequiredService<IOptions<LearningConfiguration>>()?.Value;
var maxTokens = config?.AI.MaxContextTokens ?? 4000;
// Act
var result = await llmContextService!.PrepareContextAsync("test query", maxTokens);
// Assert
Assert.IsNotNull(result);
Assert.IsTrue(result.EstimatedTokens <= maxTokens);
}
[TestMethod]
public async Task UnifiedContextService_SessionLifecycle_WorksEndToEnd()
{
// Arrange
var unifiedContextService = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var projectPath = Path.GetTempPath();
var topic = "Integration test session";
// Act
// 1. Initialize session
var sessionContext = await unifiedContextService!.InitializeLearningSessionAsync(projectPath, topic);
// 2. Store insight
await unifiedContextService.StoreLearningInsightAsync(
"Integration test insight",
"testing",
null,
new Dictionary<string, object> { ["test"] = true });
// 3. Find similar issues
var similarIssues = await unifiedContextService.FindSimilarPastIssuesAsync("integration test");
// 4. Store decision
await unifiedContextService.StoreRefactoringDecisionAsync(
"Test decision",
"Test reasoning",
"test.cs",
true);
// 5. Finalize session
var sessionSummary = await unifiedContextService.FinalizeLearningSessionAsync(
"Integration test completed",
new Dictionary<string, object> { ["duration"] = TimeSpan.FromMinutes(1) });
// Assert
Assert.IsNotNull(sessionContext);
Assert.AreEqual(projectPath, sessionContext.ProjectPath);
Assert.AreEqual(topic, sessionContext.Topic);
Assert.IsNotNull(similarIssues);
Assert.IsNotNull(sessionSummary);
Assert.AreEqual("Integration test completed", sessionSummary.Summary);
}
[TestMethod]
public async Task FullWorkflow_ComprehensiveContextPreparation_IntegratesAllServices()
{
// Arrange
var unifiedContextService = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var securityService = _serviceProvider?.GetRequiredService<ISecurityService>();
var query = "comprehensive integration test";
var filePath = Path.Combine(Path.GetTempPath(), "TestFile.cs");
// Create test file to avoid FileNotFoundException
await File.WriteAllTextAsync(filePath, @"
using System;
namespace TestProject
{
public class TestClass
{
public void TestMethod()
{
Console.WriteLine(""Hello World"");
}
}
}");
try
{
// Ensure file path is safe
Assert.IsTrue(securityService!.IsPathSafe(filePath));
// Act
var context = await unifiedContextService!.PrepareFullContextAsync(query, filePath, 6000);
// Assert
Assert.IsNotNull(context);
Assert.AreEqual(query, context.Query);
Assert.AreEqual(filePath, context.FilePath);
Assert.AreEqual(6000, context.MaxTokens);
Assert.IsNotNull(context.CurrentCodeAnalysis);
Assert.IsNotNull(context.HistoricalInsights);
Assert.IsNotNull(context.RelatedDecisions);
Assert.IsTrue(context.EstimatedTotalTokens <= 6000);
}
finally
{
// Cleanup: Delete test file
if (File.Exists(filePath))
{
File.Delete(filePath);
}
}
}
[TestMethod]
public void ServiceLifetime_Singleton_SharesInstancesCorrectly()
{
// Arrange & Act
var securityService1 = _serviceProvider?.GetRequiredService<ISecurityService>();
var securityService2 = _serviceProvider?.GetRequiredService<ISecurityService>();
var llmContextService1 = _serviceProvider?.GetRequiredService<ILLMContextService>();
var llmContextService2 = _serviceProvider?.GetRequiredService<ILLMContextService>();
var unifiedContextService1 = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var unifiedContextService2 = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
// Assert - Singleton services should be the same instance
Assert.AreSame(securityService1, securityService2);
Assert.AreSame(llmContextService1, llmContextService2);
Assert.AreSame(unifiedContextService1, unifiedContextService2);
}
[TestMethod]
public async Task ErrorHandling_ServiceExceptions_PropagateCorrectly()
{
// Arrange
var llmContextService = _serviceProvider?.GetRequiredService<ILLMContextService>();
// Act & Assert - Test exception handling for various error conditions
try
{
// This may not throw an exception as the service might handle empty queries gracefully
var result = await llmContextService!.PrepareContextAsync(string.Empty);
Assert.IsNotNull(result, "Service should handle empty query gracefully");
}
catch (Exception ex)
{
// If an exception is thrown, ensure it's of an expected type
Assert.IsTrue(ex is ArgumentException || ex is NullReferenceException || ex is InvalidOperationException,
$"Unexpected exception type: {ex.GetType()}");
}
try
{
// Test with negative max tokens
var result = await llmContextService!.PrepareContextAsync("test", -100);
Assert.IsNotNull(result, "Service should handle negative max tokens");
}
catch (Exception ex)
{
// If an exception is thrown, ensure it's of an expected type
Assert.IsTrue(ex is ArgumentException || ex is NullReferenceException || ex is InvalidOperationException,
$"Unexpected exception type: {ex.GetType()}");
}
}
[TestMethod]
public async Task Performance_ConcurrentOperations_HandleCorrectly()
{
// Arrange
var unifiedContextService = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var tasks = new List<Task<ComprehensiveContext>>();
// Act - Run multiple concurrent operations
for (int i = 0; i < 5; i++)
{
var query = $"concurrent test query {i}";
tasks.Add(unifiedContextService!.PrepareFullContextAsync(query));
}
var results = await Task.WhenAll(tasks);
// Assert
Assert.AreEqual(5, results.Length);
foreach (var result in results)
{
Assert.IsNotNull(result);
Assert.IsNotNull(result.CurrentCodeAnalysis);
}
// Verify all queries are different
var queries = results.Select(r => r.Query).ToHashSet();
Assert.AreEqual(5, queries.Count);
}
[TestMethod]
public void Configuration_Validation_WorksWithDataAnnotations()
{
// Arrange
var options = _serviceProvider?.GetRequiredService<IOptions<LearningConfiguration>>();
var securityService = _serviceProvider?.GetRequiredService<ISecurityService>();
// Act
var config = options?.Value;
var validationResult = securityService!.ValidateConfiguration(config!);
// Assert
Assert.IsNotNull(config);
Assert.IsTrue(validationResult.IsValid);
Assert.IsFalse(validationResult.Errors.Any());
}
[TestMethod]
public async Task MemoryManagement_LargeOperations_DoNotLeakMemory()
{
// Arrange
var unifiedContextService = _serviceProvider?.GetRequiredService<IUnifiedContextService>();
var initialMemory = GC.GetTotalMemory(false);
// Act - Perform several large operations
for (int i = 0; i < 10; i++)
{
var context = await unifiedContextService!.PrepareFullContextAsync($"large operation {i}", null, 8000);
Assert.IsNotNull(context);
// Force garbage collection periodically
if (i % 3 == 0)
{
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
// Final cleanup
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
var finalMemory = GC.GetTotalMemory(false);
// Assert - Memory should not have grown excessively
var memoryGrowth = finalMemory - initialMemory;
Assert.IsTrue(memoryGrowth < 50 * 1024 * 1024); // Less than 50MB growth
}
[TestMethod]
public async Task Plugin_FullIntegration_WorksWithRealServiceProvider()
{
// Arrange
using var plugin = new ComprehensiveLearningRefactorPlugin();
var tempSolutionPath = CreateTemporarySolutionFile();
var parameters = new Dictionary<string, object>
{
["solutionPath"] = tempSolutionPath,
["learningMode"] = "conservative",
["maxIterations"] = 3,
["sessionTimeoutMinutes"] = 5,
["verboseReporting"] = false,
["skipWarningsAnalysis"] = true
};
try
{
// Act
var result = await plugin.ExecuteAsync(parameters);
// Assert
Assert.IsNotNull(result);
Assert.IsNotNull(result.Message);
// The result might fail due to missing dependencies, but should not crash
}
finally
{
// Cleanup
if (File.Exists(tempSolutionPath))
{
File.Delete(tempSolutionPath);
}
}
}
private string CreateTemporarySolutionFile()
{
var tempPath = Path.GetTempFileName();
var solutionPath = Path.ChangeExtension(tempPath, ".sln");
var solutionContent = @"
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project(""{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}"") = ""TestProject"", ""TestProject.csproj"", ""{12345678-1234-1234-1234-123456789012}""
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{12345678-1234-1234-1234-123456789012}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{12345678-1234-1234-1234-123456789012}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12345678-1234-1234-1234-123456789012}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12345678-1234-1234-1234-123456789012}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal
";
File.WriteAllText(solutionPath, solutionContent);
File.Delete(tempPath);
return solutionPath;
}
}
}