diff --git a/ECMJobRunner.Application/Behaviors/CheckQueryExecutionBehavior.cs b/ECMJobRunner.Application/Behaviors/CheckQueryExecutionBehavior.cs
index 14a8083..cfd3970 100644
--- a/ECMJobRunner.Application/Behaviors/CheckQueryExecutionBehavior.cs
+++ b/ECMJobRunner.Application/Behaviors/CheckQueryExecutionBehavior.cs
@@ -1,9 +1,9 @@
using ECMJobRunner.Application.Common.Constants;
using ECMJobRunner.Application.Common.Dtos;
using ECMJobRunner.Application.Common.Exceptions;
-using ECMJobRunner.Domain.Interfaces;
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Application.Common.Options;
-using ECMJobRunner.Application.DEXJob;
+using ECMJobRunner.Application.DEXJob.Commands;
using MediatR;
using Microsoft.Extensions.Options;
using System;
diff --git a/ECMJobRunner.Application/Behaviors/MainQueryExecutionBehavior.cs b/ECMJobRunner.Application/Behaviors/MainQueryExecutionBehavior.cs
index c6b12e6..6c7ca5f 100644
--- a/ECMJobRunner.Application/Behaviors/MainQueryExecutionBehavior.cs
+++ b/ECMJobRunner.Application/Behaviors/MainQueryExecutionBehavior.cs
@@ -1,9 +1,9 @@
using ECMJobRunner.Application.Common.Constants;
using ECMJobRunner.Application.Common.Dtos;
using ECMJobRunner.Application.Common.Exceptions;
-using ECMJobRunner.Domain.Interfaces;
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Application.Common.Options;
-using ECMJobRunner.Application.DEXJob;
+using ECMJobRunner.Application.DEXJob.Commands;
using MediatR;
using Microsoft.Extensions.Options;
using System;
diff --git a/ECMJobRunner.Application/Behaviors/ReCRequestExecutionBehavior.cs b/ECMJobRunner.Application/Behaviors/ReCRequestExecutionBehavior.cs
index 26decd3..1b5455b 100644
--- a/ECMJobRunner.Application/Behaviors/ReCRequestExecutionBehavior.cs
+++ b/ECMJobRunner.Application/Behaviors/ReCRequestExecutionBehavior.cs
@@ -1,7 +1,7 @@
using ECMJobRunner.Application.Common.Constants;
using ECMJobRunner.Application.Common.Exceptions;
using ECMJobRunner.Application.Common.Options;
-using ECMJobRunner.Application.DEXJob;
+using ECMJobRunner.Application.DEXJob.Commands;
using MediatR;
using Microsoft.Extensions.Options;
using ReC.Client;
diff --git a/ECMJobRunner.Application/Common/Dtos/CfgProfileDto.cs b/ECMJobRunner.Application/Common/Dtos/CfgProfileDto.cs
new file mode 100644
index 0000000..893b384
--- /dev/null
+++ b/ECMJobRunner.Application/Common/Dtos/CfgProfileDto.cs
@@ -0,0 +1,118 @@
+using System;
+using System.Collections.Generic;
+
+namespace ECMJobRunner.Application.Common.Dtos
+{
+ ///
+ /// Data Transfer Object for CfgProfile entity
+ /// Used for querying and returning profile data
+ ///
+ public class CfgProfileDto
+ {
+ ///
+ /// Profile ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Active / Inactive switch
+ ///
+ public bool Active { get; set; }
+
+ ///
+ /// Profile name
+ ///
+ public string ProfileName { get; set; } = null!;
+
+ ///
+ /// Profile type: 0 = ADSync; 1 = GraphQL; 2 = SQL-Job; 3 = SQL and REST-Job
+ ///
+ public byte TypeId { get; set; }
+
+ ///
+ /// Schedule in Cron format
+ ///
+ public string Schedule { get; set; } = null!;
+
+ ///
+ /// Optional description
+ ///
+ public string? Comment { get; set; }
+
+ ///
+ /// Created by
+ ///
+ public string AddedWho { get; set; } = null!;
+
+ ///
+ /// Created at
+ ///
+ public DateTime AddedWhen { get; set; }
+
+ ///
+ /// Modified by
+ ///
+ public string? ChangedWho { get; set; }
+
+ ///
+ /// Modified at
+ ///
+ public DateTime? ChangedWhen { get; set; }
+
+ ///
+ /// SQL Jobs associated with this profile
+ ///
+ public List? SqlJobs { get; set; }
+ }
+
+ ///
+ /// Data Transfer Object for ProfileSqlJob entity
+ ///
+ public class ProfileSqlJobDto
+ {
+ ///
+ /// SQL Job ID
+ ///
+ public long Id { get; set; }
+
+ ///
+ /// Profile ID (foreign key)
+ ///
+ public long ProfileId { get; set; }
+
+ ///
+ /// Active / Inactive switch
+ ///
+ public bool Active { get; set; }
+
+ ///
+ /// Execution sequence order
+ ///
+ public short Sequence { get; set; }
+
+ ///
+ /// Job name
+ ///
+ public string? Name { get; set; }
+
+ ///
+ /// SQL query for pre-check validation
+ ///
+ public string? SqlCheckQuery { get; set; }
+
+ ///
+ /// Main SQL query to execute
+ ///
+ public string? SqlMainQuery { get; set; }
+
+ ///
+ /// API command to execute
+ ///
+ public string? ApiCommand { get; set; }
+
+ ///
+ /// Optional description
+ ///
+ public string? Comment { get; set; }
+ }
+}
diff --git a/ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs b/ECMJobRunner.Application/Common/Interfaces/ISQLExecutor.cs
similarity index 90%
rename from ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs
rename to ECMJobRunner.Application/Common/Interfaces/ISQLExecutor.cs
index 9059f65..14bf71b 100644
--- a/ECMJobRunner.Domain/Interfaces/ISQLExecutor.cs
+++ b/ECMJobRunner.Application/Common/Interfaces/ISQLExecutor.cs
@@ -1,7 +1,7 @@
-using System.Threading;
+using System.Threading;
using System.Threading.Tasks;
-namespace ECMJobRunner.Domain.Interfaces
+namespace ECMJobRunner.Application.Common.Interfaces
{
///
/// Interface for executing SQL queries and mapping results to DTOs
diff --git a/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs b/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs
new file mode 100644
index 0000000..7c31f5b
--- /dev/null
+++ b/ECMJobRunner.Application/Common/Mapping/ProfileMappingProfile.cs
@@ -0,0 +1,23 @@
+using AutoMapper;
+using ECMJobRunner.Application.Common.Dtos;
+using ECMJobRunner.Domain.Entities;
+
+namespace ECMJobRunner.Application.Common.Mapping
+{
+ ///
+ /// AutoMapper profile for CfgProfile and ProfileSqlJob mappings
+ /// Maps domain entities to DTOs
+ ///
+ public class ProfileMappingProfile : Profile
+ {
+ public ProfileMappingProfile()
+ {
+ // CfgProfile -> CfgProfileDto
+ CreateMap()
+ .ForMember(dest => dest.SqlJobs, opt => opt.MapFrom(src => src.SqlJobs));
+
+ // ProfileSqlJob -> ProfileSqlJobDto
+ CreateMap();
+ }
+ }
+}
diff --git a/ECMJobRunner.Application/DEXJob/Queries/GetProfileQuery.cs b/ECMJobRunner.Application/DEXJob/Queries/GetProfileQuery.cs
new file mode 100644
index 0000000..18566e1
--- /dev/null
+++ b/ECMJobRunner.Application/DEXJob/Queries/GetProfileQuery.cs
@@ -0,0 +1,124 @@
+using AutoMapper;
+using ECMJobRunner.Application.Common.Dtos;
+using ECMJobRunner.Domain.Interfaces;
+using MediatR;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ECMJobRunner.Application.DEXJob.Queries
+{
+ ///
+ /// Query to retrieve profiles with flexible filtering options
+ /// All query options are nullable - when no filters are specified, returns all profiles
+ ///
+ public class GetProfileQuery : IRequest>
+ {
+ ///
+ /// Profile ID to retrieve (optional)
+ /// When specified, returns only the profile with this ID
+ ///
+ public long? Id { get; set; }
+
+ ///
+ /// Filter by active status (optional)
+ /// When null, returns both active and inactive profiles
+ /// When true, returns only active profiles
+ /// When false, returns only inactive profiles
+ ///
+ public bool? Active { get; set; }
+
+ ///
+ /// Filter by profile type (optional)
+ /// When specified, returns only profiles with this type
+ /// Type: 0 = ADSync; 1 = GraphQL; 2 = SQL-Job; 3 = SQL and REST-Job
+ ///
+ public byte? TypeId { get; set; }
+
+ ///
+ /// Filter by profile name (optional)
+ /// When specified, returns profiles with matching name (case-insensitive contains)
+ ///
+ public string? ProfileName { get; set; }
+
+ ///
+ /// Include associated SQL jobs in the result (optional)
+ /// Default: true
+ ///
+ public bool IncludeSqlJobs { get; set; } = true;
+ }
+
+ ///
+ /// Handler for GetProfileQuery
+ /// Retrieves profiles with optional filtering and uses AutoMapper for DTO mapping
+ ///
+ public class GetProfileQueryHandler : IRequestHandler>
+ {
+ private readonly ICfgProfileRepository _profileRepository;
+ private readonly IMapper _mapper;
+
+ public GetProfileQueryHandler(ICfgProfileRepository profileRepository, IMapper mapper)
+ {
+ _profileRepository = profileRepository;
+ _mapper = mapper;
+ }
+
+ public async Task> Handle(GetProfileQuery request, CancellationToken cancellationToken)
+ {
+ IEnumerable profiles;
+
+ // If ID is specified, get single profile by ID
+ if (request.Id.HasValue)
+ {
+ var profile = request.IncludeSqlJobs
+ ? await _profileRepository.GetByIdWithSqlJobsAsync(request.Id.Value, cancellationToken)
+ : await _profileRepository.GetByIdAsync(request.Id.Value, cancellationToken);
+
+ profiles = profile != null ? new[] { profile } : [];
+ }
+ // Otherwise, get profiles with filters
+ else
+ {
+ // Get all profiles with SQL jobs if requested
+ if (request.IncludeSqlJobs)
+ {
+ // If Active filter is specified and true, use optimized method
+ if (request.Active.HasValue && request.Active.Value)
+ {
+ profiles = await _profileRepository.GetAllActiveWithSqlJobsAsync(cancellationToken);
+ }
+ else
+ {
+ // Generic query with filters
+ profiles = await _profileRepository.FindAsync(p => true, cancellationToken);
+ }
+ }
+ else
+ {
+ profiles = await _profileRepository.GetAllAsync(cancellationToken);
+ }
+
+ // Apply filters
+ if (request.Active.HasValue)
+ {
+ profiles = profiles.Where(p => p.Active == request.Active.Value);
+ }
+
+ if (request.TypeId.HasValue)
+ {
+ profiles = profiles.Where(p => p.TypeId == request.TypeId.Value);
+ }
+
+ if (!string.IsNullOrWhiteSpace(request.ProfileName))
+ {
+ var searchName = request.ProfileName.ToLowerInvariant();
+ profiles = profiles.Where(p => p.ProfileName.ToLowerInvariant().Contains(searchName));
+ }
+ }
+
+ // Use AutoMapper to map entities to DTOs
+ return _mapper.Map>(profiles.ToList());
+ }
+ }
+}
diff --git a/ECMJobRunner.Domain/Interfaces/ICfgProfileRepository.cs b/ECMJobRunner.Domain/Interfaces/ICfgProfileRepository.cs
index 06adc20..81951f4 100644
--- a/ECMJobRunner.Domain/Interfaces/ICfgProfileRepository.cs
+++ b/ECMJobRunner.Domain/Interfaces/ICfgProfileRepository.cs
@@ -1,4 +1,7 @@
using ECMJobRunner.Domain.Entities;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
namespace ECMJobRunner.Domain.Interfaces
{
@@ -7,6 +10,19 @@ namespace ECMJobRunner.Domain.Interfaces
///
public interface ICfgProfileRepository : IRepository
{
- // Add custom CfgProfile-specific methods here if needed
+ ///
+ /// Gets a profile by ID with associated SQL jobs eagerly loaded
+ ///
+ /// Profile ID
+ /// Cancellation token
+ /// Profile with SQL jobs or null if not found
+ Task GetByIdWithSqlJobsAsync(long id, CancellationToken cancellationToken = default);
+
+ ///
+ /// Gets all active profiles with associated SQL jobs eagerly loaded
+ ///
+ /// Cancellation token
+ /// List of active profiles with SQL jobs
+ Task> GetAllActiveWithSqlJobsAsync(CancellationToken cancellationToken = default);
}
}
diff --git a/ECMJobRunner.Infrastructure/DependencyExtension.cs b/ECMJobRunner.Infrastructure/DependencyExtension.cs
index 5fdb885..832c5a4 100644
--- a/ECMJobRunner.Infrastructure/DependencyExtension.cs
+++ b/ECMJobRunner.Infrastructure/DependencyExtension.cs
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
#endif
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Domain.Interfaces;
using ECMJobRunner.Infrastructure.Data;
using ECMJobRunner.Infrastructure.Repositories;
diff --git a/ECMJobRunner.Infrastructure/ECMJobRunner.Infrastructure.csproj b/ECMJobRunner.Infrastructure/ECMJobRunner.Infrastructure.csproj
index c75d7d5..a7272d7 100644
--- a/ECMJobRunner.Infrastructure/ECMJobRunner.Infrastructure.csproj
+++ b/ECMJobRunner.Infrastructure/ECMJobRunner.Infrastructure.csproj
@@ -17,6 +17,7 @@
+
@@ -28,8 +29,8 @@
-
-
+
+
diff --git a/ECMJobRunner.Infrastructure/Repositories/CfgProfileRepository.cs b/ECMJobRunner.Infrastructure/Repositories/CfgProfileRepository.cs
index 71e2375..af69dfd 100644
--- a/ECMJobRunner.Infrastructure/Repositories/CfgProfileRepository.cs
+++ b/ECMJobRunner.Infrastructure/Repositories/CfgProfileRepository.cs
@@ -2,6 +2,15 @@ using AutoMapper;
using ECMJobRunner.Domain.Entities;
using ECMJobRunner.Domain.Interfaces;
using ECMJobRunner.Infrastructure.Data;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+#if NET48
+using System.Data.Entity;
+#else
+using Microsoft.EntityFrameworkCore;
+#endif
namespace ECMJobRunner.Infrastructure.Repositories
{
@@ -17,6 +26,25 @@ namespace ECMJobRunner.Infrastructure.Repositories
{
}
- // Entity-specific methods can be added here in the future
+ ///
+ /// Gets a profile by ID with associated SQL jobs eagerly loaded
+ ///
+ public async Task GetByIdWithSqlJobsAsync(long id, CancellationToken cancellationToken = default)
+ {
+ return await _context.CfgProfiles
+ .Include(p => p.SqlJobs)
+ .FirstOrDefaultAsync(p => p.Id == id, cancellationToken);
+ }
+
+ ///
+ /// Gets all active profiles with associated SQL jobs eagerly loaded
+ ///
+ public async Task> GetAllActiveWithSqlJobsAsync(CancellationToken cancellationToken = default)
+ {
+ return await _context.CfgProfiles
+ .Include(p => p.SqlJobs)
+ .Where(p => p.Active)
+ .ToListAsync(cancellationToken);
+ }
}
}
diff --git a/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs b/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs
index 0b4d942..b13170d 100644
--- a/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs
+++ b/ECMJobRunner.Infrastructure/Services/SQLExecutor.cs
@@ -4,7 +4,7 @@ using System.Linq;
#else
using Microsoft.EntityFrameworkCore;
#endif
-using ECMJobRunner.Domain.Interfaces;
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Infrastructure.Data;
using System.Threading;
using System.Threading.Tasks;
diff --git a/ECMJobRunner.Tests/Application/CheckQueryExecutionBehaviorTests.cs b/ECMJobRunner.Tests/Application/CheckQueryExecutionBehaviorTests.cs
index 3099baf..fcf0447 100644
--- a/ECMJobRunner.Tests/Application/CheckQueryExecutionBehaviorTests.cs
+++ b/ECMJobRunner.Tests/Application/CheckQueryExecutionBehaviorTests.cs
@@ -2,9 +2,9 @@ using ECMJobRunner.Application.Behaviors;
using ECMJobRunner.Application.Common.Constants;
using ECMJobRunner.Application.Common.Dtos;
using ECMJobRunner.Application.Common.Exceptions;
-using ECMJobRunner.Domain.Interfaces;
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Application.Common.Options;
-using ECMJobRunner.Application.DEXJob;
+using ECMJobRunner.Application.DEXJob.Commands;
using ECMJobRunner.Domain.Entities;
using FluentAssertions;
using MediatR;
diff --git a/ECMJobRunner.Tests/Application/GetProfileQueryTests.cs b/ECMJobRunner.Tests/Application/GetProfileQueryTests.cs
new file mode 100644
index 0000000..485aed6
--- /dev/null
+++ b/ECMJobRunner.Tests/Application/GetProfileQueryTests.cs
@@ -0,0 +1,302 @@
+using ECMJobRunner.Application.Common.Dtos;
+using ECMJobRunner.Application.DEXJob.Queries;
+using ECMJobRunner.Domain.Entities;
+using ECMJobRunner.Domain.Interfaces;
+using FluentAssertions;
+using Moq;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Threading;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace ECMJobRunner.Tests.Application
+{
+ public class GetProfileByIdQueryTests
+ {
+ private readonly Mock _mockProfileRepo;
+ private readonly GetProfileByIdQueryHandler _handler;
+
+ public GetProfileByIdQueryTests()
+ {
+ _mockProfileRepo = new Mock();
+ _handler = new GetProfileByIdQueryHandler(_mockProfileRepo.Object);
+ }
+
+ [Fact]
+ public async Task Handle_WithValidProfileId_ReturnsProfile()
+ {
+ // Arrange
+ var profileId = 123L;
+ var profile = new CfgProfile
+ {
+ Id = profileId,
+ Active = true,
+ ProfileName = "Test Profile",
+ TypeId = 2,
+ Schedule = "0 30 4 ? * MON-SAT",
+ Comment = "Test comment",
+ AddedWho = "TestUser",
+ AddedWhen = DateTime.Now,
+ SqlJobs = new List
+ {
+ new ProfileSqlJob
+ {
+ Id = 1,
+ ProfileId = profileId,
+ Active = true,
+ Sequence = 1,
+ Name = "Job 1",
+ SqlMainQuery = "SELECT 1",
+ SqlCheckQuery = "SELECT COUNT(*) FROM Table1"
+ }
+ }
+ };
+
+ _mockProfileRepo
+ .Setup(r => r.GetByIdWithSqlJobsAsync(profileId, It.IsAny()))
+ .ReturnsAsync(profile);
+
+ var query = new GetProfileByIdQuery { ProfileId = profileId, IncludeSqlJobs = true };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result!.Id.Should().Be(profileId);
+ result.ProfileName.Should().Be("Test Profile");
+ result.Active.Should().BeTrue();
+ result.TypeId.Should().Be(2);
+ result.SqlJobs.Should().NotBeNull();
+ result.SqlJobs!.Count.Should().Be(1);
+ result.SqlJobs[0].Name.Should().Be("Job 1");
+ }
+
+ [Fact]
+ public async Task Handle_WithNonExistentProfileId_ReturnsNull()
+ {
+ // Arrange
+ var profileId = 999L;
+ _mockProfileRepo
+ .Setup(r => r.GetByIdWithSqlJobsAsync(profileId, It.IsAny()))
+ .ReturnsAsync((CfgProfile?)null);
+
+ var query = new GetProfileByIdQuery { ProfileId = profileId };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().BeNull();
+ }
+
+ [Fact]
+ public async Task Handle_WithIncludeSqlJobsFalse_DoesNotLoadSqlJobs()
+ {
+ // Arrange
+ var profileId = 123L;
+ var profile = new CfgProfile
+ {
+ Id = profileId,
+ Active = true,
+ ProfileName = "Test Profile",
+ TypeId = 2,
+ Schedule = "0 30 4 ? * MON-SAT",
+ AddedWho = "TestUser",
+ AddedWhen = DateTime.Now
+ };
+
+ _mockProfileRepo
+ .Setup(r => r.GetByIdAsync(profileId, It.IsAny()))
+ .ReturnsAsync(profile);
+
+ var query = new GetProfileByIdQuery { ProfileId = profileId, IncludeSqlJobs = false };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result!.SqlJobs.Should().BeNull();
+ _mockProfileRepo.Verify(
+ r => r.GetByIdAsync(profileId, It.IsAny()),
+ Times.Once);
+ _mockProfileRepo.Verify(
+ r => r.GetByIdWithSqlJobsAsync(It.IsAny(), It.IsAny()),
+ Times.Never);
+ }
+
+ [Fact]
+ public async Task Handle_MapsAllProperties_Correctly()
+ {
+ // Arrange
+ var profileId = 123L;
+ var addedWhen = new DateTime(2026, 1, 1, 10, 0, 0);
+ var changedWhen = new DateTime(2026, 1, 2, 14, 30, 0);
+
+ var profile = new CfgProfile
+ {
+ Id = profileId,
+ Active = false,
+ ProfileName = "Inactive Profile",
+ TypeId = 3,
+ Schedule = "0 0 12 * * ?",
+ Comment = "Detailed comment",
+ AddedWho = "Admin",
+ AddedWhen = addedWhen,
+ ChangedWho = "Editor",
+ ChangedWhen = changedWhen
+ };
+
+ _mockProfileRepo
+ .Setup(r => r.GetByIdAsync(profileId, It.IsAny()))
+ .ReturnsAsync(profile);
+
+ var query = new GetProfileByIdQuery { ProfileId = profileId, IncludeSqlJobs = false };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result!.Id.Should().Be(profileId);
+ result.Active.Should().BeFalse();
+ result.ProfileName.Should().Be("Inactive Profile");
+ result.TypeId.Should().Be(3);
+ result.Schedule.Should().Be("0 0 12 * * ?");
+ result.Comment.Should().Be("Detailed comment");
+ result.AddedWho.Should().Be("Admin");
+ result.AddedWhen.Should().Be(addedWhen);
+ result.ChangedWho.Should().Be("Editor");
+ result.ChangedWhen.Should().Be(changedWhen);
+ }
+ }
+
+ public class GetAllActiveProfilesQueryTests
+ {
+ private readonly Mock _mockProfileRepo;
+ private readonly GetAllActiveProfilesQueryHandler _handler;
+
+ public GetAllActiveProfilesQueryTests()
+ {
+ _mockProfileRepo = new Mock();
+ _handler = new GetAllActiveProfilesQueryHandler(_mockProfileRepo.Object);
+ }
+
+ [Fact]
+ public async Task Handle_ReturnsAllActiveProfiles()
+ {
+ // Arrange
+ var profiles = new List
+ {
+ new CfgProfile
+ {
+ Id = 1,
+ Active = true,
+ ProfileName = "Profile 1",
+ TypeId = 2,
+ Schedule = "0 30 4 ? * MON-SAT",
+ AddedWho = "User1",
+ AddedWhen = DateTime.Now,
+ SqlJobs = new List
+ {
+ new ProfileSqlJob { Id = 1, ProfileId = 1, Sequence = 1, Name = "Job 1" }
+ }
+ },
+ new CfgProfile
+ {
+ Id = 2,
+ Active = true,
+ ProfileName = "Profile 2",
+ TypeId = 3,
+ Schedule = "0 0 12 * * ?",
+ AddedWho = "User2",
+ AddedWhen = DateTime.Now,
+ SqlJobs = new List
+ {
+ new ProfileSqlJob { Id = 2, ProfileId = 2, Sequence = 1, Name = "Job 2" }
+ }
+ }
+ };
+
+ _mockProfileRepo
+ .Setup(r => r.GetAllActiveWithSqlJobsAsync(It.IsAny()))
+ .ReturnsAsync(profiles);
+
+ var query = new GetAllActiveProfilesQuery { IncludeSqlJobs = true };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count.Should().Be(2);
+ result[0].ProfileName.Should().Be("Profile 1");
+ result[1].ProfileName.Should().Be("Profile 2");
+ result[0].SqlJobs.Should().NotBeNull();
+ result[0].SqlJobs!.Count.Should().Be(1);
+ result[1].SqlJobs.Should().NotBeNull();
+ result[1].SqlJobs!.Count.Should().Be(1);
+ }
+
+ [Fact]
+ public async Task Handle_WithNoActiveProfiles_ReturnsEmptyList()
+ {
+ // Arrange
+ _mockProfileRepo
+ .Setup(r => r.GetAllActiveWithSqlJobsAsync(It.IsAny()))
+ .ReturnsAsync(new List());
+
+ var query = new GetAllActiveProfilesQuery();
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count.Should().Be(0);
+ }
+
+ [Fact]
+ public async Task Handle_WithIncludeSqlJobsFalse_DoesNotLoadSqlJobs()
+ {
+ // Arrange
+ var profiles = new List
+ {
+ new CfgProfile
+ {
+ Id = 1,
+ Active = true,
+ ProfileName = "Profile 1",
+ TypeId = 2,
+ Schedule = "0 30 4 ? * MON-SAT",
+ AddedWho = "User1",
+ AddedWhen = DateTime.Now
+ }
+ };
+
+ _mockProfileRepo
+ .Setup(r => r.FindAsync(It.IsAny>>(), It.IsAny()))
+ .ReturnsAsync(profiles);
+
+ var query = new GetAllActiveProfilesQuery { IncludeSqlJobs = false };
+
+ // Act
+ var result = await _handler.Handle(query, CancellationToken.None);
+
+ // Assert
+ result.Should().NotBeNull();
+ result.Count.Should().Be(1);
+ result[0].SqlJobs.Should().BeNull();
+ _mockProfileRepo.Verify(
+ r => r.FindAsync(It.IsAny>>(), It.IsAny()),
+ Times.Once);
+ _mockProfileRepo.Verify(
+ r => r.GetAllActiveWithSqlJobsAsync(It.IsAny()),
+ Times.Never);
+ }
+ }
+}
diff --git a/ECMJobRunner.Tests/Application/MainQueryExecutionBehaviorTests.cs b/ECMJobRunner.Tests/Application/MainQueryExecutionBehaviorTests.cs
index 31b0232..98cf3aa 100644
--- a/ECMJobRunner.Tests/Application/MainQueryExecutionBehaviorTests.cs
+++ b/ECMJobRunner.Tests/Application/MainQueryExecutionBehaviorTests.cs
@@ -2,9 +2,9 @@ using ECMJobRunner.Application.Behaviors;
using ECMJobRunner.Application.Common.Constants;
using ECMJobRunner.Application.Common.Dtos;
using ECMJobRunner.Application.Common.Exceptions;
-using ECMJobRunner.Domain.Interfaces;
+using ECMJobRunner.Application.Common.Interfaces;
using ECMJobRunner.Application.Common.Options;
-using ECMJobRunner.Application.DEXJob;
+using ECMJobRunner.Application.DEXJob.Commands;
using ECMJobRunner.Domain.Entities;
using FluentAssertions;
using MediatR;