453 lines
18 KiB
C#
Executable File
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;
|
|
}
|
|
}
|
|
} |