-
Notifications
You must be signed in to change notification settings - Fork 0
[ADR-206] Improve API tests #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
jodavis
merged 13 commits into
dev/claude/ADR-170-layout-processing-service
from
dev/jodavis/api-tests
May 8, 2026
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
3726aa9
Replacing a lot of custom steps in the API tests with a standard set …
ElwoodMoves 84d1338
Moving test steps and services for API tests to the same assemblies a…
ElwoodMoves 151f3d3
Remove the unnecessary ServiceContext layer
ElwoodMoves d0dd16e
Consolidate fixtures for CompiledLayoutService and RawLayoutService i…
ElwoodMoves 039f97c
Consolidate fixture for LayoutProcessingService into SimulatedEnviron…
ElwoodMoves 327920d
Attach log files to test results
ElwoodMoves a0dafcb
Clean up logging noise and improve log warning/error detection by usi…
ElwoodMoves d383275
Add more logging messages to test services
ElwoodMoves 2019e67
Fix some log-checking messages
ElwoodMoves ff38ed3
Convert async test service methods to sync, and block on async operat…
ElwoodMoves de6f318
Break down TestClientSteps into a set of related step definition file…
ElwoodMoves 231bdae
Add more comprehensive tests
ElwoodMoves 3fec2a1
Apply suggestions from code review
jodavis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
13 changes: 13 additions & 0 deletions
13
src/AdaptiveRemote.Backend.Common/AdaptiveRemote.Backend.Common.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| <Nullable>enable</Nullable> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Microsoft.Extensions.Logging" /> | ||
| </ItemGroup> | ||
|
|
||
| </Project> |
56 changes: 56 additions & 0 deletions
56
src/AdaptiveRemote.Backend.Common/Logging/FileLoggerExtensions.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace AdaptiveRemote.Backend.Common.Logging; | ||
|
|
||
| public static class FileLoggerExtensions | ||
| { | ||
| public static ILoggingBuilder AddFile(this ILoggingBuilder builder, string filePath) | ||
| { | ||
| builder.AddProvider(new SimpleFileLoggerProvider(filePath)); | ||
| return builder; | ||
| } | ||
| } | ||
|
|
||
| internal sealed class SimpleFileLoggerProvider : ILoggerProvider | ||
| { | ||
| private readonly string _filePath; | ||
| private readonly object _lock = new(); | ||
|
|
||
| public SimpleFileLoggerProvider(string filePath) | ||
| { | ||
| _filePath = filePath; | ||
| } | ||
|
|
||
| public ILogger CreateLogger(string categoryName) => new SimpleFileLogger(_filePath, _lock, categoryName); | ||
|
|
||
| public void Dispose() { } | ||
|
|
||
| private class SimpleFileLogger : ILogger | ||
| { | ||
| private readonly string _filePath; | ||
| private readonly object _lock; | ||
| private readonly string _categoryName; | ||
|
|
||
| public SimpleFileLogger(string filePath, object lockObj, string categoryName) | ||
| { | ||
| _filePath = filePath; | ||
| _lock = lockObj; | ||
| _categoryName = categoryName; | ||
| } | ||
|
|
||
| IDisposable ILogger.BeginScope<TState>(TState state) => null!; | ||
| public bool IsEnabled(LogLevel logLevel) => true; | ||
| public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter) | ||
| { | ||
| string message = $"[{DateTime.Now:O}] [{logLevel}] [{_categoryName}] {formatter(state, exception)}"; | ||
| lock (_lock) | ||
| { | ||
| File.AppendAllText(_filePath, message + "\n"); | ||
| if (exception != null) | ||
| { | ||
| File.AppendAllText(_filePath, exception + "\n"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
131 changes: 131 additions & 0 deletions
131
src/AdaptiveRemote.Backend.Common/Logging/MessageLogger.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,131 @@ | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace AdaptiveRemote.Backend.Common.Logging; | ||
|
|
||
| /// <summary> | ||
| /// Centralized logging messages for CompiledLayoutService. | ||
| /// All log messages MUST be defined here as [LoggerMessage] source-generated methods. | ||
| /// Event ID ranges: | ||
| /// 1100-1199: CompiledLayoutService | ||
| /// </summary> | ||
| public static partial class MessageLogger | ||
| { | ||
| // Common service messages | ||
| [LoggerMessage(EventId = 1100, Level = LogLevel.Information, Message = "{ServiceName} starting")] | ||
| public static partial void ServiceStarting(this ILogger logger, string serviceName); | ||
|
|
||
| [LoggerMessage(EventId = 1101, Level = LogLevel.Information, Message = "{ServiceName} started successfully on {ListenAddress}")] | ||
| public static partial void ServiceStarted(this ILogger logger, string serviceName, string listenAddress); | ||
|
|
||
| [LoggerMessage(EventId = 1102, Level = LogLevel.Information, Message = "{Method} {Path} request received for userId={UserId}")] | ||
| public static partial void AuthenticatedRequestStarted(this ILogger logger, string method, string path, string userId); | ||
|
|
||
| [LoggerMessage(EventId = 1103, Level = LogLevel.Information, Message = "{Method} {Path} request received")] | ||
| public static partial void UnauthenticatedRequestStarted(this ILogger logger, string method, string path); | ||
|
|
||
| [LoggerMessage(EventId = 1104, Level = LogLevel.Information, Message = "{Method} {Path} request handled")] | ||
| public static partial void RequestHandled(this ILogger logger, string method, string path); | ||
|
|
||
| [LoggerMessage(EventId = 1105, Level = LogLevel.Information, Message = "Health check successful")] | ||
| public static partial void HealthCheckSuccessful(this ILogger logger); | ||
|
|
||
| [LoggerMessage(EventId = 1106, Level = LogLevel.Error, Message = "Error processing health check request")] | ||
| public static partial void ErrorProcessingHealthCheck(this ILogger logger, Exception exception); | ||
|
|
||
| [LoggerMessage( | ||
| EventId = 1107, | ||
| Level = LogLevel.Error, | ||
| Message = "LocalStack dependency check failed at {HealthUrl}: {FailureReason}. LocalStack is required for local development. See docs/local-dev.md for setup instructions")] | ||
| public static partial void LocalStackDependencyUnavailable(this ILogger logger, string healthUrl, string failureReason, Exception? exception); | ||
|
|
||
| // CompiledLayoutService-specific messages | ||
| [LoggerMessage(EventId = 1301, Level = LogLevel.Information, Message = "Returning active compiled layout Id={LayoutId}")] | ||
| public static partial void ReturningActiveLayout(this ILogger logger, Guid layoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1303, Level = LogLevel.Error, Message = "Error retrieving active layout for userId={UserId}")] | ||
| public static partial void ErrorRetrievingActiveLayout(this ILogger logger, string userId, Exception exception); | ||
|
|
||
| // RawLayoutService-specific messages | ||
| [LoggerMessage(EventId = 1201, Level = LogLevel.Information, Message = "Raw layout created successfully: Id={LayoutId}")] | ||
| public static partial void RawLayoutCreated(this ILogger logger, Guid layoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1202, Level = LogLevel.Information, Message = "Raw layout updated successfully: Id={LayoutId}")] | ||
| public static partial void RawLayoutUpdated(this ILogger logger, Guid layoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1203, Level = LogLevel.Information, Message = "Raw layout deleted successfully: Id={LayoutId}")] | ||
| public static partial void RawLayoutDeleted(this ILogger logger, Guid layoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1204, Level = LogLevel.Error, Message = "Error retrieving raw layouts for userId={UserId}")] | ||
| public static partial void ErrorRetrievingRawLayouts(this ILogger logger, string userId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1205, Level = LogLevel.Error, Message = "Error retrieving raw layout Id={LayoutId} for userId={UserId}")] | ||
| public static partial void ErrorRetrievingRawLayout(this ILogger logger, Guid layoutId, string userId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1206, Level = LogLevel.Error, Message = "Error creating raw layout for userId={UserId}")] | ||
| public static partial void ErrorCreatingRawLayout(this ILogger logger, string userId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1207, Level = LogLevel.Error, Message = "Error updating raw layout Id={LayoutId} for userId={UserId}")] | ||
| public static partial void ErrorUpdatingRawLayout(this ILogger logger, Guid layoutId, string userId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1208, Level = LogLevel.Error, Message = "Error deleting raw layout Id={LayoutId} for userId={UserId}")] | ||
| public static partial void ErrorDeletingRawLayout(this ILogger logger, Guid layoutId, string userId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1209, Level = LogLevel.Information, Message = "SQS trigger enqueued; rawLayoutId={RawLayoutId} queueUrl={QueueUrl}")] | ||
| public static partial void SqsTriggerEnqueued(this ILogger logger, Guid rawLayoutId, string queueUrl); | ||
|
|
||
| [LoggerMessage(EventId = 1210, Level = LogLevel.Error, Message = "Failed to enqueue SQS trigger; rawLayoutId={RawLayoutId}")] | ||
| public static partial void ErrorEnqueuingSqsTrigger(this ILogger logger, Guid rawLayoutId, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1211, Level = LogLevel.Information, Message = "Validation result updated for raw layout Id={LayoutId}")] | ||
| public static partial void ValidationResultUpdated(this ILogger logger, Guid layoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1212, Level = LogLevel.Error, Message = "Error updating validation result for raw layout Id={LayoutId}")] | ||
| public static partial void ErrorUpdatingValidationResult(this ILogger logger, Guid layoutId, Exception exception); | ||
|
|
||
| // LayoutProcessingService-specific messages | ||
| [LoggerMessage(EventId = 1706, Level = LogLevel.Information, Message = "SQS polling loop started; queue={QueueUrl}")] | ||
| public static partial void SqsPollingStarted(this ILogger logger, string queueUrl); | ||
|
|
||
| [LoggerMessage(EventId = 1707, Level = LogLevel.Information, Message = "SQS polling loop stopped")] | ||
| public static partial void SqsPollingStopped(this ILogger logger); | ||
|
|
||
| [LoggerMessage(EventId = 1708, Level = LogLevel.Information, Message = "SQS message received; rawLayoutId={RawLayoutId} receiptHandle={ReceiptHandle}")] | ||
| public static partial void SqsMessageReceived(this ILogger logger, Guid rawLayoutId, string receiptHandle); | ||
|
|
||
| [LoggerMessage(EventId = 1709, Level = LogLevel.Information, Message = "Layout compiled successfully; rawLayoutId={RawLayoutId}")] | ||
| public static partial void LayoutCompiled(this ILogger logger, Guid rawLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1710, Level = LogLevel.Information, Message = "Layout validation passed; rawLayoutId={RawLayoutId}")] | ||
| public static partial void LayoutValidationPassed(this ILogger logger, Guid rawLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1711, Level = LogLevel.Warning, Message = "Layout validation failed; rawLayoutId={RawLayoutId} issueCount={IssueCount}")] | ||
| public static partial void LayoutValidationFailed(this ILogger logger, Guid rawLayoutId, int issueCount); | ||
|
|
||
| [LoggerMessage(EventId = 1712, Level = LogLevel.Information, Message = "Compiled layout stored; rawLayoutId={RawLayoutId} compiledLayoutId={CompiledLayoutId}")] | ||
| public static partial void CompiledLayoutStored(this ILogger logger, Guid rawLayoutId, Guid compiledLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1713, Level = LogLevel.Information, Message = "Layout-ready notification published; userId={UserId} compiledLayoutId={CompiledLayoutId}")] | ||
| public static partial void LayoutReadyPublished(this ILogger logger, string userId, Guid compiledLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1714, Level = LogLevel.Information, Message = "SQS message processed successfully; rawLayoutId={RawLayoutId}")] | ||
| public static partial void SqsMessageProcessedSuccessfully(this ILogger logger, Guid rawLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1715, Level = LogLevel.Error, Message = "Failed to process SQS message; rawLayoutId={RawLayoutId} receiptHandle={ReceiptHandle}")] | ||
| public static partial void ErrorProcessingSqsMessage(this ILogger logger, Guid rawLayoutId, string receiptHandle, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1716, Level = LogLevel.Warning, Message = "SQS message is being retried; rawLayoutId={RawLayoutId} approximateReceiveCount={ApproximateReceiveCount}")] | ||
| public static partial void SqsMessageRetry(this ILogger logger, Guid rawLayoutId, int approximateReceiveCount); | ||
|
|
||
| [LoggerMessage(EventId = 1717, Level = LogLevel.Error, Message = "SQS polling error; will retry")] | ||
| public static partial void SqsPollingError(this ILogger logger, Exception exception); | ||
|
|
||
| [LoggerMessage(EventId = 1718, Level = LogLevel.Warning, Message = "Raw layout not found; rawLayoutId={RawLayoutId}")] | ||
| public static partial void RawLayoutNotFound(this ILogger logger, Guid rawLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1719, Level = LogLevel.Information, Message = "Validation result written back to raw layout; rawLayoutId={RawLayoutId}")] | ||
| public static partial void ValidationResultWrittenBack(this ILogger logger, Guid rawLayoutId); | ||
|
|
||
| [LoggerMessage(EventId = 1720, Level = LogLevel.Warning, Message = "SQS message unrecognized and deleted; receiptHandle={ReceiptHandle}")] | ||
| public static partial void SqsUnrecognizedMessageWarning(this ILogger logger, string receiptHandle, Exception exception); | ||
|
|
||
| } |
43 changes: 43 additions & 0 deletions
43
src/AdaptiveRemote.Backend.Common/Logging/RequestHandlerScope.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| using Microsoft.Extensions.Logging; | ||
|
|
||
| namespace AdaptiveRemote.Backend.Common.Logging; | ||
|
|
||
| public static class RequestHandlerScopeExtensions | ||
| { | ||
| public static IDisposable StartRequestScope(this ILogger logger, string method, string path, string? userId = null) | ||
| { | ||
| if (userId != null) | ||
| { | ||
| logger.AuthenticatedRequestStarted(method, path, userId); | ||
| } | ||
| else | ||
| { | ||
| logger.UnauthenticatedRequestStarted(method, path); | ||
| } | ||
|
|
||
| return new RequestHandlerScope(logger, method, path); | ||
| } | ||
| } | ||
|
|
||
| internal class RequestHandlerScope : IDisposable | ||
| { | ||
| private readonly ILogger _logger; | ||
| private readonly string _method; | ||
| private readonly string _path; | ||
| private bool _disposed = false; | ||
|
|
||
| public RequestHandlerScope(ILogger logger, string method, string path) | ||
| { | ||
| _logger = logger; | ||
| _method = method; | ||
| _path = path; | ||
| } | ||
|
|
||
| public void Dispose() | ||
| { | ||
| if (!Interlocked.Exchange(ref _disposed, true)) | ||
| { | ||
| _logger.RequestHandled(_method, _path); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.