mirror of
https://github.com/ivuorinen/hiha-arvio.git
synced 2026-02-07 13:47:11 +00:00
Critical fixes per spec.md requirements: - Restructure EstimateService with two-pool algorithm (gentle vs hard shake) - Expand all estimate pools to 5x spec size for more variety: * Work gentle: 7 → 35 estimates * Work hard: 12 → 60 estimates * Generic gentle: 8 → 40 estimates * Generic hard: 15 → 75 estimates * Humorous: 9 → 45 estimates - Add NSMotionUsageDescription to iOS Info.plist (required for accelerometer) - Add code coverage enforcement to test workflow (95% minimum per spec) - Update all tests to match new two-pool selection algorithm - Use 0.5 intensity threshold to choose between gentle/hard pools All 193 tests passing. Addresses critical spec deviations identified in code review.
322 lines
11 KiB
C#
322 lines
11 KiB
C#
using FluentAssertions;
|
|
using HihaArvio.Models;
|
|
using HihaArvio.Services;
|
|
using HihaArvio.Services.Interfaces;
|
|
|
|
namespace HihaArvio.Tests.Integration;
|
|
|
|
/// <summary>
|
|
/// Integration tests verifying that services work together correctly.
|
|
/// </summary>
|
|
public class ServiceIntegrationTests : IDisposable
|
|
{
|
|
private readonly IEstimateService _estimateService;
|
|
private readonly IStorageService _storageService;
|
|
private readonly IShakeDetectionService _shakeDetectionService;
|
|
private readonly IAccelerometerService _accelerometerService;
|
|
private readonly string _testDbPath;
|
|
|
|
public ServiceIntegrationTests()
|
|
{
|
|
_estimateService = new EstimateService();
|
|
_testDbPath = Path.Combine(Path.GetTempPath(), $"test_integration_{Guid.NewGuid()}.db");
|
|
_storageService = new StorageService(_testDbPath);
|
|
_accelerometerService = new DesktopAccelerometerService();
|
|
_shakeDetectionService = new ShakeDetectionService(_accelerometerService);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (File.Exists(_testDbPath))
|
|
{
|
|
File.Delete(_testDbPath);
|
|
}
|
|
}
|
|
|
|
#region EstimateService + StorageService Integration
|
|
|
|
[Fact]
|
|
public async Task GenerateAndSaveEstimate_ShouldPersistToStorage()
|
|
{
|
|
// Arrange
|
|
var intensity = 0.75;
|
|
var duration = TimeSpan.FromSeconds(5);
|
|
var mode = EstimateMode.Work;
|
|
|
|
// Act - Generate estimate
|
|
var estimate = _estimateService.GenerateEstimate(intensity, duration, mode);
|
|
|
|
// Save to storage
|
|
await _storageService.SaveEstimateAsync(estimate);
|
|
|
|
// Load from storage
|
|
var history = await _storageService.GetHistoryAsync();
|
|
|
|
// Assert
|
|
history.Should().ContainSingle();
|
|
var saved = history.First();
|
|
saved.Id.Should().Be(estimate.Id);
|
|
saved.EstimateText.Should().Be(estimate.EstimateText);
|
|
saved.Mode.Should().Be(estimate.Mode);
|
|
saved.ShakeIntensity.Should().Be(estimate.ShakeIntensity);
|
|
saved.ShakeDuration.Should().Be(estimate.ShakeDuration);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GenerateMultipleEstimates_ShouldRespectHistoryLimit()
|
|
{
|
|
// Arrange
|
|
var settings = new AppSettings { MaxHistorySize = 3 };
|
|
await _storageService.SaveSettingsAsync(settings);
|
|
|
|
// Act - Generate and save 5 estimates
|
|
for (int i = 0; i < 5; i++)
|
|
{
|
|
var estimate = _estimateService.GenerateEstimate(0.5, TimeSpan.FromSeconds(5), EstimateMode.Work);
|
|
await _storageService.SaveEstimateAsync(estimate);
|
|
await Task.Delay(10); // Ensure different timestamps
|
|
}
|
|
|
|
// Assert - Should only keep 3 newest
|
|
var count = await _storageService.GetHistoryCountAsync();
|
|
count.Should().Be(3);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task LoadSettings_GenerateEstimate_ShouldUseCorrectMode()
|
|
{
|
|
// Arrange - Save settings with Generic mode
|
|
var settings = new AppSettings { SelectedMode = EstimateMode.Generic };
|
|
await _storageService.SaveSettingsAsync(settings);
|
|
|
|
// Act - Load settings and generate estimate
|
|
var loadedSettings = await _storageService.LoadSettingsAsync();
|
|
var estimate = _estimateService.GenerateEstimate(0.5, TimeSpan.FromSeconds(5), loadedSettings.SelectedMode);
|
|
|
|
// Assert
|
|
estimate.Mode.Should().Be(EstimateMode.Generic);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ShakeDetectionService + EstimateService Integration
|
|
|
|
[Fact]
|
|
public void DetectShake_GenerateEstimate_ShouldUseShakeData()
|
|
{
|
|
// Arrange
|
|
_shakeDetectionService.StartMonitoring();
|
|
|
|
// Act - Simulate shake
|
|
_shakeDetectionService.ProcessAccelerometerReading(3.0, 2.5, 2.0);
|
|
var shakeData = _shakeDetectionService.CurrentShakeData;
|
|
|
|
// Generate estimate based on shake
|
|
var estimate = _estimateService.GenerateEstimate(
|
|
shakeData.Intensity,
|
|
shakeData.Duration,
|
|
EstimateMode.Work);
|
|
|
|
// Assert
|
|
estimate.ShakeIntensity.Should().Be(shakeData.Intensity);
|
|
estimate.ShakeDuration.Should().Be(shakeData.Duration);
|
|
estimate.EstimateText.Should().NotBeNullOrEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public void DetectLongShake_GenerateEstimate_ShouldTriggerHumorousMode()
|
|
{
|
|
// Arrange
|
|
_shakeDetectionService.StartMonitoring();
|
|
|
|
// Act - Simulate shake and manually set long duration for test
|
|
_shakeDetectionService.ProcessAccelerometerReading(2.5, 2.0, 1.5);
|
|
|
|
// Simulate 16-second shake by generating estimate with long duration
|
|
var estimate = _estimateService.GenerateEstimate(
|
|
0.5,
|
|
TimeSpan.FromSeconds(16), // Exceeds 15-second threshold
|
|
EstimateMode.Work);
|
|
|
|
// Assert - Should switch to Humorous mode (easter egg)
|
|
estimate.Mode.Should().Be(EstimateMode.Humorous);
|
|
estimate.EstimateText.Should().NotBeNullOrEmpty("easter egg should provide a humorous estimate");
|
|
}
|
|
|
|
[Fact]
|
|
public void DetectVaryingIntensity_GenerateEstimates_ShouldProduceDifferentRanges()
|
|
{
|
|
// Arrange
|
|
_shakeDetectionService.StartMonitoring();
|
|
|
|
// Act - Test low, medium, and high intensity shakes
|
|
var estimates = new List<EstimateResult>();
|
|
|
|
// Low intensity shake
|
|
_shakeDetectionService.ProcessAccelerometerReading(1.8, 1.5, 0.5);
|
|
var lowShake = _shakeDetectionService.CurrentShakeData;
|
|
lowShake.Intensity.Should().BeLessThan(0.3);
|
|
estimates.Add(_estimateService.GenerateEstimate(lowShake.Intensity, lowShake.Duration, EstimateMode.Work));
|
|
|
|
// High intensity shake
|
|
_shakeDetectionService.Reset();
|
|
_shakeDetectionService.ProcessAccelerometerReading(4.0, 3.5, 3.0);
|
|
var highShake = _shakeDetectionService.CurrentShakeData;
|
|
highShake.Intensity.Should().BeGreaterThan(0.7);
|
|
estimates.Add(_estimateService.GenerateEstimate(highShake.Intensity, highShake.Duration, EstimateMode.Work));
|
|
|
|
// Assert - Both should generate valid estimates
|
|
estimates.Should().AllSatisfy(e =>
|
|
{
|
|
e.EstimateText.Should().NotBeNullOrEmpty();
|
|
e.Mode.Should().Be(EstimateMode.Work);
|
|
});
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Full Flow Integration
|
|
|
|
[Fact]
|
|
public async Task CompleteFlow_ShakeToEstimateToStorage_ShouldWorkEndToEnd()
|
|
{
|
|
// Arrange
|
|
var settings = await _storageService.LoadSettingsAsync();
|
|
_shakeDetectionService.StartMonitoring();
|
|
|
|
// Act - Simulate shake gesture
|
|
_shakeDetectionService.ProcessAccelerometerReading(3.0, 2.5, 2.0);
|
|
var shakeData = _shakeDetectionService.CurrentShakeData;
|
|
|
|
shakeData.IsShaking.Should().BeTrue();
|
|
|
|
// Generate estimate from shake
|
|
var estimate = _estimateService.GenerateEstimate(
|
|
shakeData.Intensity,
|
|
shakeData.Duration,
|
|
settings.SelectedMode);
|
|
|
|
// Save to storage
|
|
await _storageService.SaveEstimateAsync(estimate);
|
|
|
|
// Load from storage
|
|
var history = await _storageService.GetHistoryAsync();
|
|
|
|
// Assert - Complete flow succeeded
|
|
history.Should().ContainSingle();
|
|
var saved = history.First();
|
|
saved.Id.Should().Be(estimate.Id);
|
|
saved.EstimateText.Should().Be(estimate.EstimateText);
|
|
saved.ShakeIntensity.Should().Be(shakeData.Intensity);
|
|
|
|
// Reset for next shake
|
|
_shakeDetectionService.Reset();
|
|
_shakeDetectionService.CurrentShakeData.IsShaking.Should().BeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task MultipleShakes_ShouldAccumulateHistory()
|
|
{
|
|
// Arrange
|
|
_shakeDetectionService.StartMonitoring();
|
|
|
|
// Act - Simulate 3 shake gestures
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
// Shake
|
|
_shakeDetectionService.ProcessAccelerometerReading(2.5, 2.0, 1.5);
|
|
var shakeData = _shakeDetectionService.CurrentShakeData;
|
|
|
|
// Generate estimate
|
|
var estimate = _estimateService.GenerateEstimate(
|
|
shakeData.Intensity,
|
|
shakeData.Duration,
|
|
EstimateMode.Work);
|
|
|
|
// Save
|
|
await _storageService.SaveEstimateAsync(estimate);
|
|
|
|
// Reset for next shake
|
|
_shakeDetectionService.Reset();
|
|
|
|
await Task.Delay(10); // Ensure different timestamps
|
|
}
|
|
|
|
// Assert
|
|
var history = await _storageService.GetHistoryAsync();
|
|
history.Should().HaveCount(3);
|
|
|
|
// Verify ordering (newest first)
|
|
for (int i = 0; i < history.Count - 1; i++)
|
|
{
|
|
history[i].Timestamp.Should().BeOnOrAfter(history[i + 1].Timestamp);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ChangeMode_GenerateEstimate_ShouldUseNewMode()
|
|
{
|
|
// Arrange
|
|
var initialSettings = new AppSettings { SelectedMode = EstimateMode.Work };
|
|
await _storageService.SaveSettingsAsync(initialSettings);
|
|
|
|
// Act - Generate estimate with Work mode
|
|
var workEstimate = _estimateService.GenerateEstimate(0.5, TimeSpan.FromSeconds(5), EstimateMode.Work);
|
|
await _storageService.SaveEstimateAsync(workEstimate);
|
|
|
|
// Change mode
|
|
var newSettings = new AppSettings { SelectedMode = EstimateMode.Humorous };
|
|
await _storageService.SaveSettingsAsync(newSettings);
|
|
|
|
// Generate estimate with new mode
|
|
var humorousEstimate = _estimateService.GenerateEstimate(0.5, TimeSpan.FromSeconds(5), EstimateMode.Humorous);
|
|
await _storageService.SaveEstimateAsync(humorousEstimate);
|
|
|
|
// Assert
|
|
var history = await _storageService.GetHistoryAsync();
|
|
history.Should().HaveCount(2);
|
|
history[0].Mode.Should().Be(EstimateMode.Humorous); // Newest
|
|
history[1].Mode.Should().Be(EstimateMode.Work); // Oldest
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Event Integration
|
|
|
|
[Fact]
|
|
public async Task ShakeEvent_ShouldTriggerEstimateGeneration()
|
|
{
|
|
// Arrange
|
|
_shakeDetectionService.StartMonitoring();
|
|
EstimateResult? generatedEstimate = null;
|
|
|
|
_shakeDetectionService.ShakeDataChanged += async (sender, shakeData) =>
|
|
{
|
|
if (shakeData.IsShaking)
|
|
{
|
|
// Generate estimate when shake detected
|
|
generatedEstimate = _estimateService.GenerateEstimate(
|
|
shakeData.Intensity,
|
|
shakeData.Duration,
|
|
EstimateMode.Work);
|
|
|
|
await _storageService.SaveEstimateAsync(generatedEstimate);
|
|
}
|
|
};
|
|
|
|
// Act - Simulate shake
|
|
_shakeDetectionService.ProcessAccelerometerReading(3.0, 2.5, 2.0);
|
|
|
|
// Give event handler time to execute
|
|
await Task.Delay(50);
|
|
|
|
// Assert
|
|
generatedEstimate.Should().NotBeNull();
|
|
var history = await _storageService.GetHistoryAsync();
|
|
history.Should().ContainSingle();
|
|
history.First().Id.Should().Be(generatedEstimate!.Id);
|
|
}
|
|
|
|
#endregion
|
|
}
|