Skip to content

Commit d3f8676

Browse files
committed
test: add decorator pattern verification tests
- Create PrefrontalControllerServiceTests with 5 test cases - Verify delegation to base controller for pattern-matched queries - Verify LLM enhancement for ambiguous queries - Verify graceful fallback on LLM failures - Validate fact retrieval pattern recognition - Test direct classification delegation Tests confirm Infrastructure services properly wrap Application services, use LLM when needed, and degrade gracefully when LLM unavailable. All 50 tests passing (45 existing + 5 new)
1 parent 30e4bb8 commit d3f8676

File tree

1 file changed

+164
-0
lines changed

1 file changed

+164
-0
lines changed
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
using MemoryKit.Application.Services;
2+
using MemoryKit.Domain.Entities;
3+
using MemoryKit.Domain.Enums;
4+
using MemoryKit.Domain.Interfaces;
5+
using MemoryKit.Domain.ValueObjects;
6+
using MemoryKit.Infrastructure.Cognitive;
7+
using Microsoft.Extensions.Logging;
8+
using Moq;
9+
using Xunit;
10+
11+
namespace MemoryKit.Infrastructure.Tests.Cognitive;
12+
13+
public class PrefrontalControllerServiceTests
14+
{
15+
private readonly Mock<ISemanticKernelService> _llmMock;
16+
private readonly Mock<ILogger<PrefrontalController>> _baseLoggerMock;
17+
private readonly Mock<ILogger<PrefrontalControllerService>> _serviceLoggerMock;
18+
private readonly PrefrontalController _baseController;
19+
private readonly PrefrontalControllerService _service;
20+
21+
public PrefrontalControllerServiceTests()
22+
{
23+
_llmMock = new Mock<ISemanticKernelService>();
24+
_baseLoggerMock = new Mock<ILogger<PrefrontalController>>();
25+
_serviceLoggerMock = new Mock<ILogger<PrefrontalControllerService>>();
26+
27+
_baseController = new PrefrontalController(_baseLoggerMock.Object);
28+
_service = new PrefrontalControllerService(_baseController, _llmMock.Object, _serviceLoggerMock.Object);
29+
}
30+
31+
[Fact]
32+
public async Task BuildQueryPlanAsync_DelegatesToBaseController()
33+
{
34+
// Arrange
35+
var query = "continue our conversation";
36+
var state = new ConversationState
37+
{
38+
UserId = "user123",
39+
ConversationId = "conv123",
40+
MessageCount = 5,
41+
TurnCount = 2,
42+
ElapsedTime = TimeSpan.FromMinutes(5),
43+
QueryCount = 3,
44+
LastQueryTime = DateTime.UtcNow.AddMinutes(-1),
45+
AverageResponseTimeMs = 150,
46+
LastActivity = DateTime.UtcNow
47+
};
48+
49+
// Act
50+
var plan = await _service.BuildQueryPlanAsync(query, state, CancellationToken.None);
51+
52+
// Assert
53+
Assert.NotNull(plan);
54+
Assert.Equal(QueryType.Continuation, plan.Type);
55+
Assert.Contains(MemoryLayer.WorkingMemory, plan.LayersToUse);
56+
57+
// Verify LLM was NOT called for simple continuation query
58+
_llmMock.Verify(x => x.ClassifyQueryAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
59+
}
60+
61+
[Fact]
62+
public async Task BuildQueryPlanAsync_UsesLlmForAmbiguousQuery()
63+
{
64+
// Arrange
65+
var query = "something vague and unclear";
66+
var state = new ConversationState
67+
{
68+
UserId = "user123",
69+
ConversationId = "conv123",
70+
MessageCount = 5,
71+
TurnCount = 2,
72+
ElapsedTime = TimeSpan.FromMinutes(5),
73+
QueryCount = 3,
74+
LastQueryTime = DateTime.UtcNow.AddMinutes(-1),
75+
AverageResponseTimeMs = 150,
76+
LastActivity = DateTime.UtcNow
77+
};
78+
79+
// Mock LLM response for query classification
80+
_llmMock.Setup(x => x.ClassifyQueryAsync(
81+
It.IsAny<string>(),
82+
It.IsAny<CancellationToken>()))
83+
.ReturnsAsync("COMPLEX");
84+
85+
// Act
86+
var plan = await _service.BuildQueryPlanAsync(query, state, CancellationToken.None);
87+
88+
// Assert
89+
Assert.NotNull(plan);
90+
// Should have attempted LLM classification for ambiguous query
91+
_llmMock.Verify(x => x.ClassifyQueryAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once);
92+
}
93+
94+
[Fact]
95+
public async Task BuildQueryPlanAsync_FallsBackToBaseOnLlmFailure()
96+
{
97+
// Arrange
98+
var query = "some query";
99+
var state = new ConversationState
100+
{
101+
UserId = "user123",
102+
ConversationId = "conv123",
103+
MessageCount = 5,
104+
TurnCount = 2,
105+
ElapsedTime = TimeSpan.FromMinutes(5),
106+
QueryCount = 3,
107+
LastQueryTime = DateTime.UtcNow.AddMinutes(-1),
108+
AverageResponseTimeMs = 150,
109+
LastActivity = DateTime.UtcNow
110+
};
111+
112+
// Mock LLM to throw exception
113+
_llmMock.Setup(x => x.ClassifyQueryAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()))
114+
.ThrowsAsync(new InvalidOperationException("LLM service unavailable"));
115+
116+
// Act
117+
var plan = await _service.BuildQueryPlanAsync(query, state, CancellationToken.None);
118+
119+
// Assert - Should still get a valid plan from base controller (falls back to Complex)
120+
Assert.NotNull(plan);
121+
Assert.Equal(QueryType.Complex, plan.Type);
122+
Assert.NotNull(plan.LayersToUse);
123+
Assert.NotEmpty(plan.LayersToUse);
124+
}
125+
126+
[Fact]
127+
public async Task BuildQueryPlanAsync_RecognizesFactRetrievalPattern()
128+
{
129+
// Arrange
130+
var query = "what is the user's email address?";
131+
var state = new ConversationState
132+
{
133+
UserId = "user123",
134+
ConversationId = "conv123",
135+
MessageCount = 10,
136+
LastActivity = DateTime.UtcNow
137+
};
138+
139+
// Act
140+
var plan = await _service.BuildQueryPlanAsync(query, state, CancellationToken.None);
141+
142+
// Assert
143+
Assert.NotNull(plan);
144+
Assert.Equal(QueryType.FactRetrieval, plan.Type);
145+
Assert.Contains(MemoryLayer.WorkingMemory, plan.LayersToUse);
146+
Assert.Contains(MemoryLayer.SemanticMemory, plan.LayersToUse);
147+
}
148+
149+
[Fact]
150+
public async Task ClassifyQueryAsync_DelegatesToBaseController()
151+
{
152+
// Arrange
153+
var query = "continue the conversation";
154+
155+
// Act
156+
var queryType = await _service.ClassifyQueryAsync(query, CancellationToken.None);
157+
158+
// Assert
159+
Assert.Equal(QueryType.Continuation, queryType);
160+
161+
// Verify LLM was NOT called for pattern-matched query
162+
_llmMock.Verify(x => x.ClassifyQueryAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never);
163+
}
164+
}

0 commit comments

Comments
 (0)