From 5dc0806d32aa744403429db54f04e3a5f63c53e5 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Wed, 11 Feb 2026 21:04:21 -0800 Subject: [PATCH 1/5] removed legacy code coverage --- .github/scripts/ci/Get-CodeCoverage.ps1 | 123 ++++++++++++++++++ .../gtc-agent-standalone-web-api-sql.yml | 22 +++- src/Get-CodeCoverage.ps1 | 64 --------- .../Tests.Integration.csproj | 2 +- src/build.cmd | 5 - src/build.sh | 13 -- 6 files changed, 142 insertions(+), 87 deletions(-) create mode 100644 .github/scripts/ci/Get-CodeCoverage.ps1 delete mode 100644 src/Get-CodeCoverage.ps1 delete mode 100644 src/build.cmd delete mode 100644 src/build.sh diff --git a/.github/scripts/ci/Get-CodeCoverage.ps1 b/.github/scripts/ci/Get-CodeCoverage.ps1 new file mode 100644 index 0000000..3acad0e --- /dev/null +++ b/.github/scripts/ci/Get-CodeCoverage.ps1 @@ -0,0 +1,123 @@ +#################################################################################### +# To execute +# 1. In powershell, set security policy for this script: +# Set-ExecutionPolicy Unrestricted -Scope Process -Force +# 2. Change directory to the script folder: +# CD src (wherever your script is) +# 3. In powershell, run script: +# .\Get-CodeCoverage.ps1 -TestProjectFilter '*.Tests.csproj' -ProdPackagesOnly -ProductionAssemblies 'MyApp.Core','MyApp.Web' +# This script uses native .NET 10 code coverage (Microsoft.Testing.Platform) +# Note: Due to MSTest 4.1.0 incompatibility with 'dotnet test' on .NET 10, this runs tests as executables +#################################################################################### + +Param( + [string]$TestProjectFilter = '*.Tests.csproj', + [switch]$ProdPackagesOnly = $false, + [string[]]$ProductionAssemblies = @(), + [string]$Configuration = 'Release', + [string]$TestRootPath = '' +) +#################################################################################### +if ($IsWindows) {Set-ExecutionPolicy Unrestricted -Scope Process -Force} +$VerbosePreference = 'SilentlyContinue' # 'Continue' +#################################################################################### + +function Resolve-TestRootPath { + param( + [Parameter(Mandatory = $true)] + [System.IO.DirectoryInfo]$ScriptDir, + [Parameter(Mandatory = $false)] + [string]$OverridePath + ) + + if (-not [string]::IsNullOrWhiteSpace($OverridePath)) { + return Get-Item -Path (Resolve-Path -Path $OverridePath) + } + + $current = $ScriptDir + while ($null -ne $current) { + $srcCandidate = Join-Path $current.FullName 'src' + if (Test-Path -Path $srcCandidate) { + return Get-Item -Path $srcCandidate + } + + $current = $current.Parent + } + + return $ScriptDir +} + +# Install required tools +& dotnet tool install -g dotnet-reportgenerator-globaltool +& dotnet tool install -g dotnet-coverage + +$timestamp = Get-Date -Format "yyyyMMdd-HHmmss" +$scriptPath = Get-Item -Path $PSScriptRoot +$testRootPath = Resolve-TestRootPath -ScriptDir $scriptPath -OverridePath $TestRootPath +$reportOutputPath = Join-Path $testRootPath "TestResults\Reports\$timestamp" + +New-Item -ItemType Directory -Force -Path $reportOutputPath + +# Find test projects +$testProjects = Get-ChildItem $testRootPath -Filter $TestProjectFilter -Recurse +Write-Host "Found $($testProjects.Count) test projects." + +foreach ($project in $testProjects) { + $testProjectPath = $project.FullName + Write-Host "Running tests with coverage for project: $($project.BaseName)" + + # Use 'dotnet run' instead of 'dotnet test' for MSTest runner projects + # This bypasses the VSTest target that's incompatible with .NET 10 SDK + Push-Location $project.DirectoryName + & dotnet run --configuration $Configuration --no-build -- --coverage + Pop-Location +} + +# Collect all coverage files (Microsoft.Testing.Platform outputs .coverage files) +$coverageFiles = Get-ChildItem -Path $testRootPath -Filter "*.coverage" -Recurse | Select-Object -ExpandProperty FullName + +if ($coverageFiles.Count -eq 0) { + Write-Warning "No coverage files found. Make sure your test projects have code coverage enabled." + exit 1 +} + +Write-Host "Found $($coverageFiles.Count) coverage file(s)" + +# Convert binary .coverage files to XML format +$coverageXmlFiles = @() +foreach ($coverageFile in $coverageFiles) { + $xmlFile = $coverageFile -replace '\.coverage$', '.cobertura.xml' + Write-Host "Converting $coverageFile to XML format..." + & dotnet-coverage merge $coverageFile --output $xmlFile --output-format cobertura + if (Test-Path $xmlFile) { + $coverageXmlFiles += $xmlFile + } +} + +if ($coverageXmlFiles.Count -eq 0) { + Write-Warning "No XML coverage files were generated." + exit 1 +} + +Write-Host "Generated $($coverageXmlFiles.Count) XML coverage file(s)" + +# Generate HTML report +$coverageFilesArg = ($coverageXmlFiles -join ";") + +if ($ProdPackagesOnly) { + $assemblyFilters = ($ProductionAssemblies | ForEach-Object { "+$_" }) -join ";" + & reportgenerator -reports:$coverageFilesArg -targetdir:$reportOutputPath -reporttypes:Html -assemblyfilters:$assemblyFilters +} +else { + & reportgenerator -reports:$coverageFilesArg -targetdir:$reportOutputPath -reporttypes:Html +} + +Write-Host "Code coverage report generated at: $reportOutputPath" + +$reportIndexHtml = Join-Path $reportOutputPath "index.html" +if (Test-Path $reportIndexHtml) { + Invoke-Item -Path $reportIndexHtml +} +else { + Write-Warning "Report index.html not found at: $reportIndexHtml" +} \ No newline at end of file diff --git a/.github/workflows/gtc-agent-standalone-web-api-sql.yml b/.github/workflows/gtc-agent-standalone-web-api-sql.yml index e2e0885..55a2ae8 100644 --- a/.github/workflows/gtc-agent-standalone-web-api-sql.yml +++ b/.github/workflows/gtc-agent-standalone-web-api-sql.yml @@ -154,11 +154,21 @@ jobs: with: name: ${{ env.MIGRATION_ARTIFACT_NAME }} path: ${{ env.MIGRATION_ARTIFACT_PATH }} + if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - name: Test + run: | + cd ${{ env.SRC_PATH }}/${{ env.TEST_PATH }} + dotnet run --configuration Release --no-build + shell: pwsh + + - name: Copy test results + if: ${{ always() }} run: | mkdir -p TestResults-${{ matrix.DOTNET_VERSION }} - dotnet test ${{ env.SRC_PATH }}/${{ env.TEST_PATH }}/${{ env.TEST_PROJECT }} --configuration ${{ env.CONFIGURATION }} --results-directory TestResults-${{ matrix.DOTNET_VERSION }} --collect:"Code Coverage" --verbosity normal + if (Test-Path "${{ env.SRC_PATH }}/${{ env.TEST_PATH }}/bin/Release/net10.0/TestResults") { + Copy-Item -Path "${{ env.SRC_PATH }}/${{ env.TEST_PATH }}/bin/Release/net10.0/TestResults/*" -Destination "TestResults-${{ matrix.DOTNET_VERSION }}" -Recurse -Force + } shell: pwsh - name: Upload test results @@ -178,6 +188,7 @@ jobs: with: name: ${{ env.API_ARTIFACT_NAME }} path: ${{ env.API_ARTIFACT_OUTPUT }}/** + if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - name: Pack Blazor artifact run: | @@ -189,6 +200,7 @@ jobs: with: name: ${{ env.WEB_ARTIFACT_NAME }} path: ${{ env.WEB_ARTIFACT_OUTPUT }}/** + if: github.event_name == 'push' && github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch' - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 @@ -196,9 +208,11 @@ jobs: - name: Run Coverage Script run: | - pwsh ${{ env.SRC_PATH }}/Get-CodeCoverage.ps1 ` + pwsh ${{ env.SCRIPTS_PATH }}/ci/Get-CodeCoverage.ps1 ` -TestProjectFilter '${{ env.TEST_PROJECT }}' ` - -ProdPackagesOnly + -ProdPackagesOnly ` + -ProductionAssemblies Core.Application Presentation.WebApi Presentation.Blazor ` + -Configuration ${{ env.CONFIGURATION }} shell: pwsh - name: Upload Coverage Report @@ -220,7 +234,7 @@ jobs: matrix: DOTNET_VERSION: ["10.x"] env: - APPI_NAME: 'agent-dev-wus2-001-appi' + APPI_NAME: 'gtc-dev-wus2-001-appi' AZURE_APIAPP_NAME: agent-dev-wus2-001-api AZURE_WEBAPP_PACKAGE_PATH: '.' AZURE_WEBAPP_NAME: agent-dev-wus2-001-web diff --git a/src/Get-CodeCoverage.ps1 b/src/Get-CodeCoverage.ps1 deleted file mode 100644 index 5df8c52..0000000 --- a/src/Get-CodeCoverage.ps1 +++ /dev/null @@ -1,64 +0,0 @@ -#################################################################################### -# To execute -# 1. In powershell, set security polilcy for this script: -# Set-ExecutionPolicy Unrestricted -Scope Process -Force -# 2. Change directory to the script folder: -# CD src (wherever your script is) -# 3. In powershell, run script: -# .\Get-CodeCoverage.ps1 -TestProjectFilter 'MyTests.*.csproj' -ProdPackagesOnly -ProductionAssemblies 'MyApp.Core','MyApp.Web' -# This script is for local use to analyze code coverage in more detail using HTML report. -#################################################################################### - -Param( - [string]$TestProjectFilter = 'Tests.*.csproj', - [switch]$ProdPackagesOnly = $false, - [string[]]$ProductionAssemblies = @( - "Cannery.Insights.Core.Application", - "Cannery.Insights.Presentation.WebApi", - "Cannery.Insights.Presentation.Blazor" - ) -) -#################################################################################### -if ($IsWindows) {Set-ExecutionPolicy Unrestricted -Scope Process -Force} -$VerbosePreference = 'SilentlyContinue' # 'Continue' -#################################################################################### - -& dotnet tool install -g coverlet.console -& dotnet tool install -g dotnet-reportgenerator-globaltool - -$timestamp = Get-Date -Format "yyyyMMdd-HHmmss" -$scriptPath = Get-Item -Path $PSScriptRoot -$coverageOutputPath = Join-Path $scriptPath "TestResults\Coverage\$timestamp" -$reportOutputPath = Join-Path $scriptPath "TestResults\Reports\$timestamp" - -New-Item -ItemType Directory -Force -Path $coverageOutputPath -New-Item -ItemType Directory -Force -Path $reportOutputPath - -# Find tests for projects with 'Tests.*.csproj' -$testProjects = Get-ChildItem $scriptPath -Filter $TestProjectFilter -Recurse -Write-Host "Found $($testProjects.Count) test projects." -foreach ($project in $testProjects) { - $testProjectPath = $project.FullName - Write-Host "Running tests for project: $($testProjectPath)" - - $buildOutput = Join-Path -Path $project.Directory.FullName -ChildPath "bin\Debug\net9.0\$($project.BaseName).dll" - $coverageFile = Join-Path $coverageOutputPath "coverage.cobertura.xml" - Write-Host "Analyzing code coverage for: $buildOutput" - coverlet $buildOutput --target "dotnet" --targetargs "test $($project.FullName) --no-build" --format cobertura --output $coverageFile - -} - -# Generate HTML report -if ($ProdPackagesOnly) { - $assemblyFilters = ($ProductionAssemblies | ForEach-Object { "+$_" }) -join ";" - $assemblyFilters = ($ProductionAssemblies | ForEach-Object { "+$_" }) -join ";" - & reportgenerator -reports:"$coverageOutputPath/**/coverage.cobertura.xml" -targetdir:$reportOutputPath -reporttypes:Html -assemblyfilters:$assemblyFilters -} -else { - & reportgenerator -reports:"$coverageOutputPath/**/coverage.cobertura.xml" -targetdir:$reportOutputPath -reporttypes:Html -} - -Write-Host "Code coverage report generated at: $reportOutputPath" - -$reportIndexHtml = Join-Path $reportOutputPath "index.html" -Invoke-Item -Path $reportIndexHtml \ No newline at end of file diff --git a/src/Tests.Integration/Tests.Integration.csproj b/src/Tests.Integration/Tests.Integration.csproj index 9c3128f..b94e24a 100644 --- a/src/Tests.Integration/Tests.Integration.csproj +++ b/src/Tests.Integration/Tests.Integration.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/build.cmd b/src/build.cmd deleted file mode 100644 index ae89052..0000000 --- a/src/build.cmd +++ /dev/null @@ -1,5 +0,0 @@ -@echo off -setlocal -cd "%~dp0" -dotnet build --configuration Release --interactive ^ - && dotnet test --configuration Release --no-build --no-restore --interactive \ No newline at end of file diff --git a/src/build.sh b/src/build.sh deleted file mode 100644 index fa86913..0000000 --- a/src/build.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -e - -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) - -pushd "$SCRIPT_DIR" > /dev/null - -# Release config triggers also "dotnet format" -dotnet build --configuration Release --interactive -dotnet test --configuration Release --no-build --no-restore --interactive - -popd > /dev/null \ No newline at end of file From fa1557aeab7c34e7346b40f281be5c13bf57022b Mon Sep 17 00:00:00 2001 From: Robert Good Date: Wed, 11 Feb 2026 21:23:49 -0800 Subject: [PATCH 2/5] code coverage and standards --- .github/scripts/ci/Get-CodeCoverage.ps1 | 7 +++++-- .../Abstractions/IAgentFrameworkContext.cs | 2 +- src/Core.Application/Actor/SaveMyActorCommand.cs | 2 +- .../ChatCompletion/CreateMyChatMessageCommand.cs | 2 +- .../ChatCompletion/CreateMyChatSessionCommand.cs | 8 ++++---- .../ChatCompletion/CreateMyChatSessionCommandValidator.cs | 2 +- .../ChatCompletion/GetMyChatMessagesPaginatedQuery.cs | 2 +- .../ChatCompletion/GetMyChatSessionsQuery.cs | 2 +- .../Common/Behaviors/CustomPerformanceBehavior.cs | 4 ++-- src/Core.Application/GlobalUsings.cs | 4 ++-- src/Core.Domain/Actor/ActorEntity.cs | 8 ++++---- src/Core.Domain/Auth/IUserContext.cs | 2 +- src/Core.Domain/Auth/UserContext.cs | 2 +- src/Core.Domain/ChatCompletion/ChatSessionEntity.cs | 6 +++--- src/Infrastructure.AgentFramework/ConfigureServices.cs | 2 +- src/Infrastructure.AgentFramework/Tools/ActorsTool.cs | 4 ++-- .../Tools/ChatMessagesTool.cs | 4 ++-- .../Tools/ChatSessionsTool.cs | 4 ++-- .../Persistence/AgentFrameworkContext.cs | 4 ++-- .../Actor/GetActorByOwnerIdQueryStepDefinitions.cs | 4 ++-- src/Tests.Integration/Agent/MockAIAgent.cs | 4 ++-- src/Tests.Integration/Agent/MockAgentSession.cs | 4 ++-- .../CreateChatSessionCommandStepDefinitions.cs | 8 ++++---- .../GetChatMessagesPaginatedQueryStepDefinitions.cs | 2 +- .../GetMyChatSessionQueryStepDefinitions.cs | 4 ++-- .../GetMyChatSessionsPaginatedQueryStepDefinitions.cs | 4 ++-- .../GetMyChatSessionsQueryStepDefinitions.cs | 4 ++-- src/Tests.Integration/GlobalUsings.cs | 4 ++-- src/Tests.Integration/TestBase.cs | 6 +++--- 29 files changed, 59 insertions(+), 56 deletions(-) diff --git a/.github/scripts/ci/Get-CodeCoverage.ps1 b/.github/scripts/ci/Get-CodeCoverage.ps1 index 3acad0e..8e3d16a 100644 --- a/.github/scripts/ci/Get-CodeCoverage.ps1 +++ b/.github/scripts/ci/Get-CodeCoverage.ps1 @@ -5,13 +5,13 @@ # 2. Change directory to the script folder: # CD src (wherever your script is) # 3. In powershell, run script: -# .\Get-CodeCoverage.ps1 -TestProjectFilter '*.Tests.csproj' -ProdPackagesOnly -ProductionAssemblies 'MyApp.Core','MyApp.Web' +# .\Get-CodeCoverage.ps1 -TestProjectFilter '*Tests*.csproj' -ProdPackagesOnly -ProductionAssemblies 'MyApp.Core','MyApp.Web' # This script uses native .NET 10 code coverage (Microsoft.Testing.Platform) # Note: Due to MSTest 4.1.0 incompatibility with 'dotnet test' on .NET 10, this runs tests as executables #################################################################################### Param( - [string]$TestProjectFilter = '*.Tests.csproj', + [string]$TestProjectFilter = '*Tests*.csproj', [switch]$ProdPackagesOnly = $false, [string[]]$ProductionAssemblies = @(), [string]$Configuration = 'Release', @@ -65,6 +65,9 @@ Write-Host "Found $($testProjects.Count) test projects." foreach ($project in $testProjects) { $testProjectPath = $project.FullName Write-Host "Running tests with coverage for project: $($project.BaseName)" + + Write-Host "Building test project: $($project.BaseName)" + & dotnet build $testProjectPath --configuration $Configuration # Use 'dotnet run' instead of 'dotnet test' for MSTest runner projects # This bypasses the VSTest target that's incompatible with .NET 10 SDK diff --git a/src/Core.Application/Abstractions/IAgentFrameworkContext.cs b/src/Core.Application/Abstractions/IAgentFrameworkContext.cs index 863cbeb..1fe2cc6 100644 --- a/src/Core.Application/Abstractions/IAgentFrameworkContext.cs +++ b/src/Core.Application/Abstractions/IAgentFrameworkContext.cs @@ -7,7 +7,7 @@ namespace Goodtocode.AgentFramework.Core.Application.Abstractions; public interface IAgentFrameworkContext { DbSet ChatMessages { get; } - DbSet ChatSessions {get; } + DbSet ChatSessions { get; } DbSet Actors { get; } Task SaveChangesAsync(CancellationToken cancellationToken = default); diff --git a/src/Core.Application/Actor/SaveMyActorCommand.cs b/src/Core.Application/Actor/SaveMyActorCommand.cs index 0cd0937..e42180d 100644 --- a/src/Core.Application/Actor/SaveMyActorCommand.cs +++ b/src/Core.Application/Actor/SaveMyActorCommand.cs @@ -29,7 +29,7 @@ public async Task Handle(SaveMyActorCommand request, CancellationToken } else { - actor = ActorEntity.Create(Guid.NewGuid(), request?.FirstName, request?.LastName, request?.Email); + actor = ActorEntity.Create(Guid.NewGuid(), request?.FirstName, request?.LastName, request?.Email); _context.Actors.Add(actor); } diff --git a/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs index c2d2730..1670758 100644 --- a/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs +++ b/src/Core.Application/ChatCompletion/CreateMyChatMessageCommand.cs @@ -108,6 +108,6 @@ private static void GuardAgainstUnauthorizedUser(ChatSessionEntity chatSession, private static void GuardAgainstNullAgentResponse(ChatMessage? response) { if (response == null) - throw new CustomValidationException([new("ChatMessage","Agent response cannot be null")]); + throw new CustomValidationException([new("ChatMessage", "Agent response cannot be null")]); } } diff --git a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs index 72e6580..395ba31 100644 --- a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs +++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommand.cs @@ -30,15 +30,15 @@ public async Task Handle(CreateMyChatSessionCommand request, Can GuardAgainstEmptyUser(request?.UserContext); var actor = await _context.Actors - .FirstOrDefaultAsync(a => a.OwnerId == request!.UserContext!.OwnerId + .FirstOrDefaultAsync(a => a.OwnerId == request!.UserContext!.OwnerId && a.TenantId == request.UserContext.TenantId, cancellationToken); - + if (actor == null) { actor = ActorEntity.Create( Guid.NewGuid(), - request?.UserContext?.FirstName, - request?.UserContext?.LastName, + request?.UserContext?.FirstName, + request?.UserContext?.LastName, request?.UserContext?.Email ); _context.Actors.Add(actor); diff --git a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs index 047aad8..e9bb047 100644 --- a/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs +++ b/src/Core.Application/ChatCompletion/CreateMyChatSessionCommandValidator.cs @@ -3,7 +3,7 @@ public class CreateMyChatSessionCommandValidator : Validator { public CreateMyChatSessionCommandValidator() - { + { RuleFor(x => x.Message) .NotEmpty("Message is required"); diff --git a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs index 877e4c8..b2c9e26 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatMessagesPaginatedQuery.cs @@ -22,7 +22,7 @@ public async Task> Handle(GetMyChatMessagesPaginat { GuardAgainstEmptyUser(request?.UserContext); - var userContext = request!.UserContext!; + var userContext = request!.UserContext!; var returnData = await _context.ChatMessages .Where(x => x.ChatSession != null && x.ChatSession.OwnerId == userContext.OwnerId) diff --git a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs index 064fa81..eb4d217 100644 --- a/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs +++ b/src/Core.Application/ChatCompletion/GetMyChatSessionsQuery.cs @@ -21,7 +21,7 @@ public async Task> Handle(GetMyChatSessionsQuery req var startDate = request?.StartDate; var endDate = request?.EndDate; - var userContext = request?.UserContext; + var userContext = request?.UserContext; var returnData = await _context.ChatSessions .Where(x => userContext != null && x.OwnerId == userContext.OwnerId) diff --git a/src/Core.Application/Common/Behaviors/CustomPerformanceBehavior.cs b/src/Core.Application/Common/Behaviors/CustomPerformanceBehavior.cs index f912e92..eb10e06 100644 --- a/src/Core.Application/Common/Behaviors/CustomPerformanceBehavior.cs +++ b/src/Core.Application/Common/Behaviors/CustomPerformanceBehavior.cs @@ -1,5 +1,5 @@ -using Microsoft.Extensions.Logging; -using System.Diagnostics; +using System.Diagnostics; +using Microsoft.Extensions.Logging; namespace Goodtocode.AgentFramework.Core.Application.Common.Behaviors; diff --git a/src/Core.Application/GlobalUsings.cs b/src/Core.Application/GlobalUsings.cs index 2d73215..a37d375 100644 --- a/src/Core.Application/GlobalUsings.cs +++ b/src/Core.Application/GlobalUsings.cs @@ -1,4 +1,4 @@ -global using Goodtocode.Validation; +global using System.Reflection; global using Goodtocode.Mediator; +global using Goodtocode.Validation; global using Microsoft.EntityFrameworkCore; -global using System.Reflection; diff --git a/src/Core.Domain/Actor/ActorEntity.cs b/src/Core.Domain/Actor/ActorEntity.cs index 8edaffc..65daa24 100644 --- a/src/Core.Domain/Actor/ActorEntity.cs +++ b/src/Core.Domain/Actor/ActorEntity.cs @@ -15,7 +15,7 @@ public static ActorEntity Create(Guid id, string? firstName, string? lastName, s { return new ActorEntity { - Id = id == Guid.Empty ? Guid.NewGuid() : id, + Id = id == Guid.Empty ? Guid.NewGuid() : id, FirstName = firstName, LastName = lastName, Email = email @@ -37,8 +37,8 @@ public static ActorEntity Create(IUserContext userInfo) public void Update(string? firstName, string? lastName, string? email) { - FirstName = firstName ?? FirstName; - LastName = lastName ?? LastName; - Email = email ?? Email; + FirstName = firstName ?? FirstName; + LastName = lastName ?? LastName; + Email = email ?? Email; } } diff --git a/src/Core.Domain/Auth/IUserContext.cs b/src/Core.Domain/Auth/IUserContext.cs index 8756855..48258ca 100644 --- a/src/Core.Domain/Auth/IUserContext.cs +++ b/src/Core.Domain/Auth/IUserContext.cs @@ -12,7 +12,7 @@ public interface IUserContext string FirstName { get; } string LastName { get; } string Email { get; } - IEnumerable Roles { get; } + IEnumerable Roles { get; } bool CanView { get; } bool CanEdit { get; } bool CanDelete { get; } diff --git a/src/Core.Domain/Auth/UserContext.cs b/src/Core.Domain/Auth/UserContext.cs index 2372034..915d3c5 100644 --- a/src/Core.Domain/Auth/UserContext.cs +++ b/src/Core.Domain/Auth/UserContext.cs @@ -4,7 +4,7 @@ public struct UserRoles { public const string ChatOwner = "AssetOwner"; public const string ChatEditor = "AssetEditor"; - public const string ChatViewer = "AssetViewer"; + public const string ChatViewer = "AssetViewer"; } /// diff --git a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs index 5d790a9..a78642a 100644 --- a/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs +++ b/src/Core.Domain/ChatCompletion/ChatSessionEntity.cs @@ -1,5 +1,5 @@ -using Goodtocode.Domain.Entities; -using Goodtocode.AgentFramework.Core.Domain.Actor; +using Goodtocode.AgentFramework.Core.Domain.Actor; +using Goodtocode.Domain.Entities; namespace Goodtocode.AgentFramework.Core.Domain.ChatCompletion; @@ -27,6 +27,6 @@ public static ChatSessionEntity Create(Guid id, Guid actorId, string? title, Cha public void Update(string? title) { - Title = title ?? Title; + Title = title ?? Title; } } diff --git a/src/Infrastructure.AgentFramework/ConfigureServices.cs b/src/Infrastructure.AgentFramework/ConfigureServices.cs index c8e67e0..620446d 100644 --- a/src/Infrastructure.AgentFramework/ConfigureServices.cs +++ b/src/Infrastructure.AgentFramework/ConfigureServices.cs @@ -42,7 +42,7 @@ public static IServiceCollection AddAgentFrameworkOpenAIServices(this IServiceCo }; var agentOptions = new ChatClientAgentOptions - { + { Name = "CopilotAgent", Description = "Microsoft Agent Framework Quick-start Copilot", }; diff --git a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs index fc98c91..b143853 100644 --- a/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ActorsTool.cs @@ -1,8 +1,8 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; +using System.ComponentModel; +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; -using System.ComponentModel; namespace Goodtocode.AgentFramework.Infrastructure.AgentFramework.Tools; diff --git a/src/Infrastructure.AgentFramework/Tools/ChatMessagesTool.cs b/src/Infrastructure.AgentFramework/Tools/ChatMessagesTool.cs index 63d4ff0..9e2e7b2 100644 --- a/src/Infrastructure.AgentFramework/Tools/ChatMessagesTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ChatMessagesTool.cs @@ -1,8 +1,8 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; +using System.ComponentModel; +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; -using System.ComponentModel; namespace Goodtocode.AgentFramework.Infrastructure.AgentFramework.Tools; diff --git a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs index 6660bb1..54d9edf 100644 --- a/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs +++ b/src/Infrastructure.AgentFramework/Tools/ChatSessionsTool.cs @@ -1,8 +1,8 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; +using System.ComponentModel; +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.AI; using Microsoft.Extensions.DependencyInjection; -using System.ComponentModel; namespace Goodtocode.AgentFramework.Infrastructure.AgentFramework.Tools; diff --git a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs index c800ce6..c2923bb 100644 --- a/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs +++ b/src/Infrastructure.SqlServer/Persistence/AgentFrameworkContext.cs @@ -1,9 +1,9 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; +using System.Reflection; +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Goodtocode.AgentFramework.Core.Domain.Actor; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; using Goodtocode.Domain.Entities; using Microsoft.EntityFrameworkCore.ChangeTracking; -using System.Reflection; namespace Goodtocode.AgentFramework.Infrastructure.SqlServer.Persistence; diff --git a/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs index 931cc02..a5c1eff 100644 --- a/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs +++ b/src/Tests.Integration/Actor/GetActorByOwnerIdQueryStepDefinitions.cs @@ -38,9 +38,9 @@ public async Task WhenIGetAAuthor() await context.SaveChangesAsync(CancellationToken.None); } - var request = new GetMyActorQuery() + var request = new GetMyActorQuery() { - UserContext = userContext + UserContext = userContext }; var validator = new GetMyActorQueryValidator(); diff --git a/src/Tests.Integration/Agent/MockAIAgent.cs b/src/Tests.Integration/Agent/MockAIAgent.cs index cc338d5..bf57947 100644 --- a/src/Tests.Integration/Agent/MockAIAgent.cs +++ b/src/Tests.Integration/Agent/MockAIAgent.cs @@ -1,6 +1,6 @@ -using Microsoft.Agents.AI; +using System.Text.Json; +using Microsoft.Agents.AI; using Microsoft.Extensions.AI; -using System.Text.Json; namespace Goodtocode.AgentFramework.Tests.Integration.Agent; diff --git a/src/Tests.Integration/Agent/MockAgentSession.cs b/src/Tests.Integration/Agent/MockAgentSession.cs index af6ad51..3fdd586 100644 --- a/src/Tests.Integration/Agent/MockAgentSession.cs +++ b/src/Tests.Integration/Agent/MockAgentSession.cs @@ -1,5 +1,5 @@ -using Microsoft.Agents.AI; -using System.Text.Json; +using System.Text.Json; +using Microsoft.Agents.AI; namespace Goodtocode.AgentFramework.Tests.Integration.Agent; diff --git a/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs index d8bf02b..9ae5034 100644 --- a/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/CreateChatSessionCommandStepDefinitions.cs @@ -42,11 +42,11 @@ public async Task WhenICreateAChatSessionWithTheMessage() { // Setup the database if want to test existing records var actor = ActorEntity.Create(_actorId, "Test", "Actor", "actor@goodtocode.com"); - context.Actors.Add(actor); + context.Actors.Add(actor); if (_exists) { var chatSession = ChatSessionEntity.Create(_id, _actorId, "Test Session", ChatMessageRole.assistant, _message, "First Response"); - context.ChatSessions.Add(chatSession); + context.ChatSessions.Add(chatSession); } await context.SaveChangesAsync(CancellationToken.None); @@ -54,7 +54,7 @@ public async Task WhenICreateAChatSessionWithTheMessage() var request = new CreateMyChatSessionCommand() { Id = _id, - Title = def, + Title = def, Message = _message, UserContext = userContext }; @@ -65,7 +65,7 @@ public async Task WhenICreateAChatSessionWithTheMessage() if (validationResponse.IsValid) { try - { + { var handler = new CreateChatSessionCommandHandler(agent, context); await handler.Handle(request, CancellationToken.None); responseType = CommandResponseType.Successful; diff --git a/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs index dfbe554..b32c635 100644 --- a/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetChatMessagesPaginatedQueryStepDefinitions.cs @@ -1,6 +1,6 @@ +using Goodtocode.AgentFramework.Core.Application.ChatCompletion; using Goodtocode.AgentFramework.Core.Application.Common.Models; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion { diff --git a/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs index c719d20..1135fa8 100644 --- a/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionQueryStepDefinitions.cs @@ -1,6 +1,6 @@ -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; using System.Globalization; using Goodtocode.AgentFramework.Core.Application.ChatCompletion; +using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion; @@ -51,7 +51,7 @@ public async Task WhenIGetAChatSession() var request = new GetMyChatSessionQuery() { Id = _id, - UserContext = userContext + UserContext = userContext }; var validator = new GetMyChatSessionQueryValidator(); diff --git a/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs index b44a32a..d2e9120 100644 --- a/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsPaginatedQueryStepDefinitions.cs @@ -1,6 +1,6 @@ +using Goodtocode.AgentFramework.Core.Application.ChatCompletion; using Goodtocode.AgentFramework.Core.Application.Common.Models; using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; -using Goodtocode.AgentFramework.Core.Application.ChatCompletion; namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion { @@ -76,7 +76,7 @@ public async Task WhenIGetTheChatSessionsPaginated() PageSize = _pageSize, StartDate = _startDate == default ? null : _startDate, EndDate = _endDate == default ? null : _endDate, - UserContext = userContext + UserContext = userContext }; var validator = new GetMyChatSessionsPaginatedQueryValidator(); diff --git a/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs index e4770e8..d19e028 100644 --- a/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs +++ b/src/Tests.Integration/ChatCompletion/GetMyChatSessionsQueryStepDefinitions.cs @@ -1,5 +1,5 @@ -using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; using Goodtocode.AgentFramework.Core.Application.ChatCompletion; +using Goodtocode.AgentFramework.Core.Domain.ChatCompletion; namespace Goodtocode.AgentFramework.Tests.Integration.ChatCompletion; @@ -60,7 +60,7 @@ public async Task WhenIGetTheChatSessions() { StartDate = _startDate == default ? null : _startDate, EndDate = _endDate == default ? null : _endDate, - UserContext = userContext + UserContext = userContext }; var validator = new GetMyChatSessionsQueryValidator(); diff --git a/src/Tests.Integration/GlobalUsings.cs b/src/Tests.Integration/GlobalUsings.cs index 8db9d8f..7d34879 100644 --- a/src/Tests.Integration/GlobalUsings.cs +++ b/src/Tests.Integration/GlobalUsings.cs @@ -1,5 +1,5 @@ -global using Goodtocode.Assertion; +global using System.Collections.Concurrent; +global using Goodtocode.Assertion; global using Goodtocode.Validation; global using Microsoft.EntityFrameworkCore; global using Reqnroll; -global using System.Collections.Concurrent; diff --git a/src/Tests.Integration/TestBase.cs b/src/Tests.Integration/TestBase.cs index b969b2d..5fe7bbb 100644 --- a/src/Tests.Integration/TestBase.cs +++ b/src/Tests.Integration/TestBase.cs @@ -1,4 +1,5 @@ -using Goodtocode.AgentFramework.Core.Application.Abstractions; +using System.Reflection; +using Goodtocode.AgentFramework.Core.Application.Abstractions; using Goodtocode.AgentFramework.Core.Application.Common.Exceptions; using Goodtocode.AgentFramework.Core.Domain.Auth; using Goodtocode.AgentFramework.Infrastructure.AgentFramework.Options; @@ -7,7 +8,6 @@ using Microsoft.Agents.AI; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using System.Reflection; namespace Goodtocode.AgentFramework.Tests.Integration; @@ -31,7 +31,7 @@ public enum CommandResponseType internal IConfiguration configuration; internal MockAIAgent agent = new(); internal OpenAIOptions optionsOpenAi = new(); - internal UserContext userContext = UserContext.Create(firstName: "John", lastName: "Doe", email: "john.doe@goodtocode.com", + internal UserContext userContext = UserContext.Create(firstName: "John", lastName: "Doe", email: "john.doe@goodtocode.com", ownerId: Guid.NewGuid(), tenantId: Guid.NewGuid(), roles: ["Admin"]); private readonly ICurrentUserContext? _currentUserContext; From 84dc28dcb5e968783b36c45280b58bf59a7afc51 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Wed, 11 Feb 2026 21:32:39 -0800 Subject: [PATCH 3/5] test environment based --- .github/workflows/gtc-agent-standalone-web-api-sql.yml | 4 ++-- src/.github/copilot-instructions.md | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 src/.github/copilot-instructions.md diff --git a/.github/workflows/gtc-agent-standalone-web-api-sql.yml b/.github/workflows/gtc-agent-standalone-web-api-sql.yml index 55a2ae8..c371a1a 100644 --- a/.github/workflows/gtc-agent-standalone-web-api-sql.yml +++ b/.github/workflows/gtc-agent-standalone-web-api-sql.yml @@ -159,7 +159,7 @@ jobs: - name: Test run: | cd ${{ env.SRC_PATH }}/${{ env.TEST_PATH }} - dotnet run --configuration Release --no-build + dotnet run --configuration ${{ env.CONFIGURATION }} --no-build shell: pwsh - name: Copy test results @@ -234,7 +234,7 @@ jobs: matrix: DOTNET_VERSION: ["10.x"] env: - APPI_NAME: 'gtc-dev-wus2-001-appi' + APPI_NAME: 'agent-dev-wus2-001-appi' AZURE_APIAPP_NAME: agent-dev-wus2-001-api AZURE_WEBAPP_PACKAGE_PATH: '.' AZURE_WEBAPP_NAME: agent-dev-wus2-001-web diff --git a/src/.github/copilot-instructions.md b/src/.github/copilot-instructions.md deleted file mode 100644 index 35edb6d..0000000 --- a/src/.github/copilot-instructions.md +++ /dev/null @@ -1,4 +0,0 @@ -# Copilot Instructions - -## General Guidelines -- Use custom .NotEmpty("message") validation convention instead of .NotEmpty().WithMessage("message") in FluentValidation validators. The Goodtocode.Validation library extends FluentValidation with custom syntax where validation methods accept message as a parameter: .NotEmpty("message"), .NotEqual(value, "message"), etc. Do not use .WithMessage() - pass the message directly as a parameter to the validation method. \ No newline at end of file From e7dbb685b5dbd04bf618090564ff06dde1ea004e Mon Sep 17 00:00:00 2001 From: Robert Good Date: Wed, 11 Feb 2026 22:16:28 -0800 Subject: [PATCH 4/5] override testroot --- .github/scripts/ci/Get-CodeCoverage.ps1 | 13 ++----------- .../workflows/gtc-agent-standalone-web-api-sql.yml | 5 ++--- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/.github/scripts/ci/Get-CodeCoverage.ps1 b/.github/scripts/ci/Get-CodeCoverage.ps1 index 8e3d16a..6b81a0e 100644 --- a/.github/scripts/ci/Get-CodeCoverage.ps1 +++ b/.github/scripts/ci/Get-CodeCoverage.ps1 @@ -5,15 +5,13 @@ # 2. Change directory to the script folder: # CD src (wherever your script is) # 3. In powershell, run script: -# .\Get-CodeCoverage.ps1 -TestProjectFilter '*Tests*.csproj' -ProdPackagesOnly -ProductionAssemblies 'MyApp.Core','MyApp.Web' +# .\Get-CodeCoverage.ps1 -TestProjectFilter '*Tests*.csproj' # This script uses native .NET 10 code coverage (Microsoft.Testing.Platform) # Note: Due to MSTest 4.1.0 incompatibility with 'dotnet test' on .NET 10, this runs tests as executables #################################################################################### Param( [string]$TestProjectFilter = '*Tests*.csproj', - [switch]$ProdPackagesOnly = $false, - [string[]]$ProductionAssemblies = @(), [string]$Configuration = 'Release', [string]$TestRootPath = '' ) @@ -106,14 +104,7 @@ Write-Host "Generated $($coverageXmlFiles.Count) XML coverage file(s)" # Generate HTML report $coverageFilesArg = ($coverageXmlFiles -join ";") - -if ($ProdPackagesOnly) { - $assemblyFilters = ($ProductionAssemblies | ForEach-Object { "+$_" }) -join ";" - & reportgenerator -reports:$coverageFilesArg -targetdir:$reportOutputPath -reporttypes:Html -assemblyfilters:$assemblyFilters -} -else { - & reportgenerator -reports:$coverageFilesArg -targetdir:$reportOutputPath -reporttypes:Html -} +& reportgenerator -reports:$coverageFilesArg -targetdir:$reportOutputPath -reporttypes:Html Write-Host "Code coverage report generated at: $reportOutputPath" diff --git a/.github/workflows/gtc-agent-standalone-web-api-sql.yml b/.github/workflows/gtc-agent-standalone-web-api-sql.yml index c371a1a..cdba3cf 100644 --- a/.github/workflows/gtc-agent-standalone-web-api-sql.yml +++ b/.github/workflows/gtc-agent-standalone-web-api-sql.yml @@ -210,9 +210,8 @@ jobs: run: | pwsh ${{ env.SCRIPTS_PATH }}/ci/Get-CodeCoverage.ps1 ` -TestProjectFilter '${{ env.TEST_PROJECT }}' ` - -ProdPackagesOnly ` - -ProductionAssemblies Core.Application Presentation.WebApi Presentation.Blazor ` - -Configuration ${{ env.CONFIGURATION }} + -Configuration '${{ env.CONFIGURATION }}' ` + -TestRootPath '${{ env.SRC_PATH }}' shell: pwsh - name: Upload Coverage Report From 021ef49d08245a613d83569396609893788db567 Mon Sep 17 00:00:00 2001 From: Robert Good Date: Wed, 11 Feb 2026 22:51:22 -0800 Subject: [PATCH 5/5] soft fail --- .github/scripts/ci/Get-CodeCoverage.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/ci/Get-CodeCoverage.ps1 b/.github/scripts/ci/Get-CodeCoverage.ps1 index 6b81a0e..31214f5 100644 --- a/.github/scripts/ci/Get-CodeCoverage.ps1 +++ b/.github/scripts/ci/Get-CodeCoverage.ps1 @@ -79,7 +79,7 @@ $coverageFiles = Get-ChildItem -Path $testRootPath -Filter "*.coverage" -Recurse if ($coverageFiles.Count -eq 0) { Write-Warning "No coverage files found. Make sure your test projects have code coverage enabled." - exit 1 + exit 0 } Write-Host "Found $($coverageFiles.Count) coverage file(s)" @@ -97,7 +97,7 @@ foreach ($coverageFile in $coverageFiles) { if ($coverageXmlFiles.Count -eq 0) { Write-Warning "No XML coverage files were generated." - exit 1 + exit 0 } Write-Host "Generated $($coverageXmlFiles.Count) XML coverage file(s)"