Skip to content

Conversation

@Krishanthaudayakumara
Copy link
Owner

…d TestServerBuilder pattern to the TestWebApplicationFactory approach

Description / Motivation

Testing

  • The Unit & Intergration tests are passing.
  • I have added the necessary tests to cover my changes.

Terms

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR migrates test fixtures from the deprecated TestServerBuilder pattern to the modern TestWebApplicationFactory<T> approach for integration testing. The migration updates multiple test fixtures to use a centralized factory with program classes, improving test consistency and maintainability.

Key changes:

  • Replaced TestServerBuilder with TestWebApplicationFactory<T> across all integration test fixtures
  • Created dedicated test program classes for different testing scenarios (tracking, binding, rendering, etc.)
  • Consolidated mock HTTP client handling in the factory
  • Updated test fixtures to use dependency injection through IClassFixture<T>

Reviewed Changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
TestWebApplicationFactory.cs Enhanced factory with reflection-based host creation and specialized tracking/middleware configurations
TrackingProxyFixture.cs Migrated from TestServer to factory pattern with TestTrackingProgram
TrackingFixture.cs Updated to use TestSimpleTrackingProgram through factory
TestTrackingProgram.cs New program class for tracking with visitor identification middleware
TestSimpleTrackingProgram.cs New program class for simple tracking scenarios
AttributeBasedTrackingFixture.cs Migrated to factory pattern using TestTrackingProgram
Various test fixtures Updated to use factory pattern with appropriate test program classes

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

var createHostBuilderMethod = typeof(T).GetMethod("CreateHostBuilder", BindingFlags.Public | BindingFlags.Static);
if (createHostBuilderMethod != null)
{
return createHostBuilderMethod.Invoke(null, new object[] { Array.Empty<string>() }) as IHostBuilder;
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method invocation could return null but is cast to IHostBuilder without null checking. Add null check before casting or use a safer cast operation.

Suggested change
return createHostBuilderMethod.Invoke(null, new object[] { Array.Empty<string>() }) as IHostBuilder;
var result = createHostBuilderMethod.Invoke(null, new object[] { Array.Empty<string>() });
if (result is IHostBuilder hostBuilder)
{
return hostBuilder;
}
// If the method returns null or an unexpected type, fall back to base implementation
return base.CreateHostBuilder();

Copilot uses AI. Check for mistakes.
Comment on lines +104 to +107
var lastIp = ips.LastOrDefault()?.Trim();
if (!string.IsNullOrEmpty(lastIp))
{
message.Headers.Add("X-Forwarded-For", lastIp);
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using LastOrDefault() to get the last IP from X-Forwarded-For header is incorrect. The first IP in the list represents the original client IP, not the last one.

Suggested change
var lastIp = ips.LastOrDefault()?.Trim();
if (!string.IsNullOrEmpty(lastIp))
{
message.Headers.Add("X-Forwarded-For", lastIp);
var firstIp = ips.FirstOrDefault()?.Trim();
if (!string.IsNullOrEmpty(firstIp))
{
message.Headers.Add("X-Forwarded-For", firstIp);

Copilot uses AI. Check for mistakes.
});

// Configure visitor identification options for tracking tests
bool isTrackingProgram = typeof(T).Name == "TestTrackingProgram";
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded string comparisons with type names are fragile and error-prone. Consider using type-based checks like typeof(T) == typeof(TestTrackingProgram) or implementing a marker interface.

Suggested change
bool isTrackingProgram = typeof(T).Name == "TestTrackingProgram";
bool isTrackingProgram = typeof(T) == typeof(TestTrackingProgram);

Copilot uses AI. Check for mistakes.
else
{
// For tracking programs, add visitor identification middleware before routing
bool isTrackingProgram = typeof(T).Name == "TestTrackingProgram";
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded string comparisons with type names are fragile and error-prone. Consider using type-based checks like typeof(T) == typeof(TestTrackingProgram) or implementing a marker interface.

Suggested change
bool isTrackingProgram = typeof(T).Name == "TestTrackingProgram";
bool isTrackingProgram = typeof(T) == typeof(TestTrackingProgram);

Copilot uses AI. Check for mistakes.

// TestBasicProgram and TestTrackingProgram handle their own global middleware configuration
// Only add global rendering engine middleware for programs that don't configure it themselves
bool isBasicProgram = typeof(T).Name == "TestBasicProgram";
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hard-coded string comparisons with type names are fragile and error-prone. Consider using type-based checks like typeof(T) == typeof(TestTrackingProgram) or implementing a marker interface.

Copilot uses AI. Check for mistakes.
{
// Build the correct path
var fullPath = context.Request.Path + context.Request.QueryString;
var finalUrl = new Uri(visitorIdOptions.Value.SitecoreInstanceUri, fullPath.ToString());
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling ToString() on PathString + QueryString concatenation is unnecessary. The PathString type already handles implicit string conversion properly.

Suggested change
var finalUrl = new Uri(visitorIdOptions.Value.SitecoreInstanceUri, fullPath.ToString());
var finalUrl = new Uri(visitorIdOptions.Value.SitecoreInstanceUri, fullPath);

Copilot uses AI. Check for mistakes.
.AddHttpHandler("mock", serviceProvider =>
{
// This will be configured by TestWebApplicationFactory
throw new NotImplementedException("Mock handler should be configured by TestWebApplicationFactory");
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using NotImplementedException in configuration is misleading since this indicates missing functionality rather than intentional delegation. Consider using a more descriptive exception type or comment.

Suggested change
throw new NotImplementedException("Mock handler should be configured by TestWebApplicationFactory");
throw new InvalidOperationException("Mock handler should be configured by TestWebApplicationFactory");

Copilot uses AI. Check for mistakes.
.AddHttpHandler("mock", serviceProvider =>
{
// This will be configured by TestWebApplicationFactory
throw new NotImplementedException("Mock handler should be configured by TestWebApplicationFactory");
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using NotImplementedException in configuration is misleading since this indicates missing functionality rather than intentional delegation. Consider using a more descriptive exception type or comment.

Suggested change
throw new NotImplementedException("Mock handler should be configured by TestWebApplicationFactory");
throw new InvalidOperationException("Mock handler should be configured by TestWebApplicationFactory");

Copilot uses AI. Check for mistakes.
Comment on lines +53 to +56
await Assert.ThrowsAsync<KeyNotFoundException>(() =>
{
return Task.FromException(new KeyNotFoundException("invalidkey"));
});
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test implementation is incorrect. It's creating a task that always throws rather than testing actual functionality. The test should verify the real message configuration error scenario.

Copilot uses AI. Check for mistakes.
HtmlDocument doc = new();
doc.LoadHtml(response);
HtmlNode? sectionNode = doc.DocumentNode.ChildNodes.First(n => n.HasClass("component-6"));
HtmlNode? sectionNode = doc.DocumentNode.Descendants().FirstOrDefault(n => n.HasClass("component-6"));
Copy link

Copilot AI Sep 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from ChildNodes.First() to Descendants().FirstOrDefault() which fundamentally alters the search behavior. This may not find the intended element if the DOM structure is different.

Suggested change
HtmlNode? sectionNode = doc.DocumentNode.Descendants().FirstOrDefault(n => n.HasClass("component-6"));
HtmlNode? sectionNode = doc.DocumentNode.ChildNodes.FirstOrDefault(n => n.NodeType == HtmlNodeType.Element && n.HasClass("component-6"));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants