[Autoloop: test_coverage] Increase unit test coverage#362
Conversation
Add GetVitalsDocumentByDate tests to Vitals.Svc CosmosRepository covering success, null result, empty iterator, and exception paths. Add HealthDataService retry and error path tests to Reporting.Svc covering factory failure, retry on error response, persistent failure, empty tool response, dispose verification, and parameter validation. Coverage: 82.29% (5256/6387 lines) Run: https://github.com/willvelida/biotrackr/actions/runs/25556971386 Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> agent: github-copilot model: Claude Sonnet 4.6 contribution: test-generation
1 similar comment
There was a problem hiding this comment.
Pull request overview
This PR increases unit test coverage across services, focusing on previously uncovered error/retry branches in the Vitals Cosmos repository and Reporting health data aggregation logic.
Changes:
- Added unit tests for
CosmosRepository.GetVitalsDocumentByDateinBiotrackr.Vitals.Svccovering success, empty results, and exception paths. - Added unit tests for
HealthDataService.FetchHealthDataAsyncinBiotrackr.Reporting.Svccovering retry behavior, empty/invalid responses, dispose behavior, and parameter passing.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/Biotrackr.Vitals.Svc/Biotrackr.Vitals.Svc.UnitTests/RepositoryTests/CosmosRepositoryShould.cs | Adds coverage for GetVitalsDocumentByDate query result and exception branches. |
| src/Biotrackr.Reporting.Svc/Biotrackr.Reporting.Svc.UnitTests/Services/HealthDataServiceShould.cs | Adds coverage for MCP retry/error handling paths and verifies tool-call behavior. |
| [Fact] | ||
| public async Task FetchHealthDataAsync_ShouldRetryOnce_WhenFirstAttemptReturnsError() | ||
| { | ||
| // Arrange | ||
| var errorResponse = JsonSerializer.Serialize(new { error = "Upstream API returned 500" }); | ||
| var validResponse = BuildPageResponse([new { date = "2024-01-01", steps = 5000 }]); | ||
|
|
||
| var activityCallCount = 0; | ||
| _mcpToolCallerMock | ||
| .Setup(x => x.CallToolAsync("get_activity_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(() => | ||
| { | ||
| activityCallCount++; | ||
| return activityCallCount == 1 ? errorResponse : validResponse; | ||
| }); | ||
|
|
||
| var singlePageResponse = BuildPageResponse([new { value = 1 }]); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_food_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(singlePageResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_sleep_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(singlePageResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_vitals_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(singlePageResponse); | ||
|
|
||
| var service = CreateService(); | ||
|
|
||
| // Act | ||
| var result = await service.FetchHealthDataAsync("2024-01-01", "2024-01-07", CancellationToken.None); | ||
|
|
||
| // Assert | ||
| result.Activity.Should().Contain("5000"); | ||
| activityCallCount.Should().BeGreaterThanOrEqualTo(2); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task FetchHealthDataAsync_ShouldReturnEmptyItems_WhenBothAttemptsReturnError() | ||
| { | ||
| // Arrange | ||
| var errorResponse = JsonSerializer.Serialize(new { error = "Persistent failure" }); | ||
| var validResponse = BuildPageResponse([new { value = 1 }]); | ||
|
|
||
| _mcpToolCallerMock | ||
| .Setup(x => x.CallToolAsync("get_activity_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(errorResponse); | ||
|
|
||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_food_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_sleep_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_vitals_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
|
|
||
| var service = CreateService(); | ||
|
|
||
| // Act | ||
| var result = await service.FetchHealthDataAsync("2024-01-01", "2024-01-07", CancellationToken.None); | ||
|
|
||
| // Assert | ||
| result.Activity.Should().Contain("\"totalCount\":0"); |
|
|
||
| // Assert | ||
| result.Activity.Should().Contain("5000"); | ||
| activityCallCount.Should().BeGreaterThanOrEqualTo(2); |
| [Fact] | ||
| public async Task FetchHealthDataAsync_ShouldHandleEmptyString_WhenToolReturnsEmpty() | ||
| { | ||
| // Arrange | ||
| _mcpToolCallerMock | ||
| .Setup(x => x.CallToolAsync(It.IsAny<string>(), It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(string.Empty); | ||
|
|
||
| var service = CreateService(); | ||
|
|
||
| // Act | ||
| var result = await service.FetchHealthDataAsync("2024-01-01", "2024-01-07", CancellationToken.None); | ||
|
|
||
| // Assert | ||
| result.Should().NotBeNull(); | ||
| result.Activity.Should().Contain("\"totalCount\":0"); | ||
| } |
| [Fact] | ||
| public async Task FetchHealthDataAsync_ShouldReturnEmptyItems_WhenBothAttemptsReturnError() | ||
| { | ||
| // Arrange | ||
| var errorResponse = JsonSerializer.Serialize(new { error = "Persistent failure" }); | ||
| var validResponse = BuildPageResponse([new { value = 1 }]); | ||
|
|
||
| _mcpToolCallerMock | ||
| .Setup(x => x.CallToolAsync("get_activity_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(errorResponse); | ||
|
|
||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_food_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_sleep_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
| _mcpToolCallerMock.Setup(x => x.CallToolAsync("get_vitals_by_date_range", It.IsAny<Dictionary<string, object?>>(), It.IsAny<CancellationToken>())) | ||
| .ReturnsAsync(validResponse); | ||
|
|
||
| var service = CreateService(); | ||
|
|
||
| // Act | ||
| var result = await service.FetchHealthDataAsync("2024-01-01", "2024-01-07", CancellationToken.None); | ||
|
|
||
| // Assert | ||
| result.Activity.Should().Contain("\"totalCount\":0"); | ||
| } |
|
Warning The Autoloop Iteration 2 — test_coverageChange: Added 27 unit tests for previously untested UI model classes:
Result: All 259 UI unit tests pass (38 model tests, 27 new). Coverage improvement is marginal since model properties are single-line. Larger gains require testing DataPageBase component logic and service error paths. Next priorities: DataPageBase bUnit tests, ChatApiService error paths. |
Autoloop program
test_coverage— iteratively increasing unit test line coverage toward 85% target.Iteration 1
Coverage: 82.29% (5256/6387 lines)
Changes
GetVitalsDocumentByDate(success, null result, empty iterator, exception paths) → 100% coveragePer-Service Coverage
Warning
Firewall blocked 3 domains
The following domains were blocked by the firewall during workflow execution:
169.254.169.254management.azure.comunreachableserver.example.comSee Network Configuration for more information.