Skip to content

Commit cfcc1ba

Browse files
committed
Add simulated error for exceptions in view and error page (#7958)
Progress on #7868
1 parent 678fe47 commit cfcc1ba

10 files changed

Lines changed: 180 additions & 25 deletions

File tree

src/NuGetGallery/App_Start/DefaultDependenciesModule.cs

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
using System.Net.Http;
1212
using System.Net.Mail;
1313
using System.Security.Principal;
14+
using System.Threading;
1415
using System.Threading.Tasks;
1516
using System.Web;
1617
using System.Web.Hosting;
@@ -171,7 +172,7 @@ protected override void Load(ContainerBuilder builder)
171172
.InstancePerLifetimeScope();
172173

173174
var galleryDbConnectionFactory = CreateDbConnectionFactory(
174-
diagnosticsService,
175+
loggerFactory,
175176
nameof(EntitiesContext),
176177
configuration.Current.SqlConnectionString,
177178
secretInjector);
@@ -262,10 +263,10 @@ protected override void Load(ContainerBuilder builder)
262263
.As<IEntityRepository<PackageDeprecation>>()
263264
.InstancePerLifetimeScope();
264265

265-
ConfigureGalleryReadOnlyReplicaEntitiesContext(builder, diagnosticsService, configuration, secretInjector);
266+
ConfigureGalleryReadOnlyReplicaEntitiesContext(builder, loggerFactory, configuration, secretInjector);
266267

267268
var supportDbConnectionFactory = CreateDbConnectionFactory(
268-
diagnosticsService,
269+
loggerFactory,
269270
nameof(SupportRequestDbContext),
270271
configuration.Current.SqlConnectionStringSupportRequest,
271272
secretInjector);
@@ -451,7 +452,7 @@ protected override void Load(ContainerBuilder builder)
451452
break;
452453
}
453454

454-
RegisterAsynchronousValidation(builder, diagnosticsService, configuration, secretInjector);
455+
RegisterAsynchronousValidation(builder, loggerFactory, configuration, secretInjector);
455456

456457
RegisterAuditingServices(builder, defaultAuditingService);
457458

@@ -829,10 +830,13 @@ private static void RegisterAsynchronousEmailMessagingService(ContainerBuilder b
829830
.InstancePerDependency();
830831
}
831832

832-
private static ISqlConnectionFactory CreateDbConnectionFactory(IDiagnosticsService diagnostics, string name,
833-
string connectionString, ISecretInjector secretInjector)
833+
private static ISqlConnectionFactory CreateDbConnectionFactory(
834+
ILoggerFactory loggerFactory,
835+
string name,
836+
string connectionString,
837+
ISecretInjector secretInjector)
834838
{
835-
var logger = diagnostics.SafeGetSource($"AzureSqlConnectionFactory-{name}");
839+
var logger = loggerFactory.CreateLogger($"AzureSqlConnectionFactory-{name}");
836840
return new AzureSqlConnectionFactory(connectionString, secretInjector, logger);
837841
}
838842

@@ -841,13 +845,14 @@ private static DbConnection CreateDbConnection(ISqlConnectionFactory connectionF
841845
return Task.Run(() => connectionFactory.CreateAsync()).Result;
842846
}
843847

844-
private static void ConfigureGalleryReadOnlyReplicaEntitiesContext(ContainerBuilder builder,
845-
IDiagnosticsService diagnostics,
848+
private static void ConfigureGalleryReadOnlyReplicaEntitiesContext(
849+
ContainerBuilder builder,
850+
ILoggerFactory loggerFactory,
846851
ConfigurationService configuration,
847852
ISecretInjector secretInjector)
848853
{
849854
var galleryDbReadOnlyReplicaConnectionFactory = CreateDbConnectionFactory(
850-
diagnostics,
855+
loggerFactory,
851856
nameof(ReadOnlyEntitiesContext),
852857
configuration.Current.SqlReadOnlyReplicaConnectionString ?? configuration.Current.SqlConnectionString,
853858
secretInjector);
@@ -861,11 +866,14 @@ private static void ConfigureGalleryReadOnlyReplicaEntitiesContext(ContainerBuil
861866
.InstancePerLifetimeScope();
862867
}
863868

864-
private static void ConfigureValidationEntitiesContext(ContainerBuilder builder, IDiagnosticsService diagnostics,
865-
ConfigurationService configuration, ISecretInjector secretInjector)
869+
private static void ConfigureValidationEntitiesContext(
870+
ContainerBuilder builder,
871+
ILoggerFactory loggerFactory,
872+
ConfigurationService configuration,
873+
ISecretInjector secretInjector)
866874
{
867875
var validationDbConnectionFactory = CreateDbConnectionFactory(
868-
diagnostics,
876+
loggerFactory,
869877
nameof(ValidationEntitiesContext),
870878
configuration.Current.SqlConnectionStringValidation,
871879
secretInjector);
@@ -887,8 +895,11 @@ private static void ConfigureValidationEntitiesContext(ContainerBuilder builder,
887895
.InstancePerLifetimeScope();
888896
}
889897

890-
private void RegisterAsynchronousValidation(ContainerBuilder builder, IDiagnosticsService diagnostics,
891-
ConfigurationService configuration, ISecretInjector secretInjector)
898+
private void RegisterAsynchronousValidation(
899+
ContainerBuilder builder,
900+
ILoggerFactory loggerFactory,
901+
ConfigurationService configuration,
902+
ISecretInjector secretInjector)
892903
{
893904
builder
894905
.RegisterType<NuGet.Services.Validation.ServiceBusMessageSerializer>()
@@ -914,7 +925,7 @@ private void RegisterAsynchronousValidation(ContainerBuilder builder, IDiagnosti
914925

915926
if (configuration.Current.AsynchronousPackageValidationEnabled)
916927
{
917-
ConfigureValidationEntitiesContext(builder, diagnostics, configuration, secretInjector);
928+
ConfigureValidationEntitiesContext(builder, loggerFactory, configuration, secretInjector);
918929

919930
builder
920931
.Register(c =>

src/NuGetGallery/Controllers/PagesController.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,13 @@ public virtual async Task<ActionResult> Privacy()
159159
[HttpGet]
160160
public virtual ActionResult SimulateError(SimulatedErrorType type = SimulatedErrorType.Exception)
161161
{
162-
return type.MapToMvcResult();
162+
switch (type)
163+
{
164+
case SimulatedErrorType.ExceptionInView:
165+
return View(type);
166+
default:
167+
return type.MapToMvcResult();
168+
}
163169
}
164170
}
165171
}

src/NuGetGallery/Extensions/SimulatedErrorTypeExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ public static ActionResult MapToMvcResult(this SimulatedErrorType type)
5959
throw type.MapToException();
6060
}
6161
}
62+
6263
public static string GetMessage(this SimulatedErrorType type)
6364
{
6465
return $"{nameof(SimulatedErrorType)} {type}";
@@ -98,6 +99,9 @@ public static Exception MapToException(this SimulatedErrorType type)
9899
ReasonPhrase = message,
99100
});
100101
case SimulatedErrorType.Exception:
102+
case SimulatedErrorType.ExceptionInView:
103+
case SimulatedErrorType.ExceptionInInlineErrorPage:
104+
case SimulatedErrorType.ExceptionInDedicatedErrorPage:
101105
return new Exception(message);
102106
case SimulatedErrorType.UserSafeException:
103107
return new UserSafeException(message);

src/NuGetGallery/NuGetGallery.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1854,7 +1854,6 @@
18541854
<Content Include="Areas\Admin\Views\ApiKeys\Index.cshtml" />
18551855
<Content Include="App_Data\Files\Content\OData-Cache-Configuration.json" />
18561856
<Content Include="App_Data\Files\Content\GitHubUsage.v1.json" />
1857-
<None Include="Properties\PublishProfiles\nuget-staging-frontend.pubxml" />
18581857
<Content Include="Scripts\gallery\async-file-upload.js" />
18591858
<Content Include="Scripts\gallery\autocomplete.js" />
18601859
<Content Include="Scripts\gallery\bootstrap.js" />
@@ -1934,6 +1933,7 @@
19341933
<Content Include="Views\Experiments\SearchSideBySide.cshtml" />
19351934
<Content Include="Views\Api\HealthProbeApi.cshtml" />
19361935
<Content Include="Views\Pages\_Enable2FA.cshtml" />
1936+
<Content Include="Views\Pages\SimulateError.cshtml" />
19371937
</ItemGroup>
19381938
<ItemGroup>
19391939
<Service Include="{508349B6-6B84-4DF5-91F0-309BEEBAD82D}" />

src/NuGetGallery/RequestModels/SimulatedErrorType.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,8 @@ public enum SimulatedErrorType
2020
Exception,
2121
UserSafeException,
2222
ReadOnlyMode,
23+
ExceptionInView,
24+
ExceptionInInlineErrorPage,
25+
ExceptionInDedicatedErrorPage,
2326
}
2427
}

src/NuGetGallery/Views/Errors/InternalError.cshtml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@
33
Response.StatusCode = 500;
44
}
55

6+
@if (StringComparer.OrdinalIgnoreCase.Equals(Request.Path, "/pages/simulate-error")
7+
&& StringComparer.OrdinalIgnoreCase.Equals(Request.QueryString["type"], SimulatedErrorType.ExceptionInInlineErrorPage.ToString()))
8+
{
9+
throw SimulatedErrorType.ExceptionInInlineErrorPage.MapToException();
10+
}
11+
12+
@if (StringComparer.OrdinalIgnoreCase.Equals(Request.Path, "/Errors/500")
13+
&& Request.Cookies["simulatedErrorType"] != null
14+
&& StringComparer.OrdinalIgnoreCase.Equals(Request.Cookies["simulatedErrorType"].Value, SimulatedErrorType.ExceptionInDedicatedErrorPage.ToString()))
15+
{
16+
throw SimulatedErrorType.ExceptionInDedicatedErrorPage.MapToException();
17+
}
18+
619
@ViewHelpers.ErrorPage(Url, Html, "500", "Internal Server Error", @<text>
720
<p>An error occurred while processing your request. We really messed up this time...</p></text>, @<text>
821
<p>We have logged the error and will look into it so that it doesn't happen again; however, if you keep seeing this page, please <a href="https://github.com/NuGet/NuGetGallery/issues">file a bug!</a></p></text>)
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@model SimulatedErrorType?
2+
3+
<section role="main" class="container main-container">
4+
@if (Model == SimulatedErrorType.ExceptionInView)
5+
{
6+
throw Model.Value.MapToException();
7+
}
8+
</section>

tests/NuGetGallery.FunctionalTests/ErrorHandling/ErrorHandlingTests.cs

Lines changed: 101 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@ namespace NuGetGallery.FunctionalTests.ErrorHandling
1515
{
1616
public class ErrorHandlingTests : GalleryTestBase, IDisposable
1717
{
18+
private readonly CookieContainer _cookieContainer;
1819
private readonly HttpClientHandler _httpClientHandler;
1920
private readonly HttpClient _httpClient;
2021

2122
public ErrorHandlingTests(ITestOutputHelper testOutputHelper) : base(testOutputHelper)
2223
{
24+
_cookieContainer = new CookieContainer();
2325
_httpClientHandler = new HttpClientHandler
2426
{
2527
AllowAutoRedirect = false,
26-
UseCookies = false,
28+
UseCookies = true,
29+
CookieContainer = _cookieContainer,
2730
};
2831
_httpClient = new HttpClient(_httpClientHandler);
2932
}
@@ -34,11 +37,11 @@ public ErrorHandlingTests(ITestOutputHelper testOutputHelper) : base(testOutputH
3437
[Theory]
3538
[Priority(2)]
3639
[Category("P2Tests")]
37-
[InlineData("__Controller::TempData", "Message=You successfully uploaded z̡̜͍̈̍̐̃̊͋́a̜̣͍̬̞̝͉̽ͧ͗l̸̖͕̤̠̹̘͖̃̌ͤg͓̝͓̰̀ͪo͈͌ 1.0.0.")]
38-
public async Task RejectedCookie(string name, string value)
40+
[MemberData(nameof(RejectedCookies))]
41+
public async Task RejectedCookie(string relativePath, string name, string value, Action<TestResponse> validate)
3942
{
4043
// Arrange
41-
var relativePath = $"/packages/{Constants.TestPackageId}";
44+
_httpClientHandler.UseCookies = false;
4245
var requestUri = GetRequestUri(relativePath);
4346
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
4447
{
@@ -48,10 +51,23 @@ public async Task RejectedCookie(string name, string value)
4851
var response = await GetTestResponseAsync(relativePath, request);
4952

5053
// Assert
51-
Validator.SimpleHtml(HttpStatusCode.BadRequest)(response);
54+
validate(response);
5255
}
5356
}
5457

58+
public static IEnumerable<object[]> RejectedCookies => new[]
59+
{
60+
new object[] { $"/packages/{Constants.TestPackageId}", "__Controller::TempData", "Message=You successfully uploaded z̡̜͍̈̍̐̃̊͋́a̜̣͍̬̞̝͉̽ͧ͗l̸̖͕̤̠̹̘͖̃̌ͤg͓̝͓̰̀ͪo͈͌ 1.0.0.", Validator.SimpleHtml(HttpStatusCode.BadRequest) },
61+
62+
new object[] { $"/packages/{Constants.TestPackageId}", "__Controller::TempData", "Message=<script>alert(1)</script>", Validator.Redirect("500") },
63+
64+
new object[] { $"/packages/{Constants.TestPackageId}", "__Controller::TempData", "<script>alert(1)</script>", Validator.Redirect("500") },
65+
66+
new object[] { "/packages", "nugetab", "<script>alert(1)</script>", Validator.PrettyInternalServerError() },
67+
68+
new object[] { "/packages", "nugetab", "z̡̜͍̈̍̐̃̊͋́a̜̣͍̬̞̝͉̽ͧ͗l̸̖͕̤̠̹̘͖̃̌ͤg͓̝͓̰̀ͪo͈͌", Validator.SimpleHtml(HttpStatusCode.BadRequest) },
69+
};
70+
5571
/// <summary>
5672
/// Verify the behavior when a URL with restricted characters is used.
5773
/// </summary>
@@ -93,6 +109,60 @@ public async Task PageThatDoesNotExist(string relativePath)
93109
Validator.PrettyHtml(HttpStatusCode.NotFound)(response);
94110
}
95111

112+
[Fact]
113+
[Priority(2)]
114+
[Category("P2Tests")]
115+
public async Task DefaultErrorPageBehavior()
116+
{
117+
// Arrange & Act
118+
var response = await GetTestResponseAsync("/Errors/500");
119+
120+
// Assert
121+
Validator.PrettyInternalServerError()(response);
122+
}
123+
124+
/// <summary>
125+
/// Verify behavior when the pretty HTTP 500 page fails itself.
126+
/// </summary>
127+
[Fact]
128+
[Priority(2)]
129+
[Category("P2Tests")]
130+
public async Task ErrorInErrorPageWithoutPath()
131+
{
132+
// Arrange
133+
var cookies = new SimulatedErrorRequest(EndpointType.Pages, SimulatedErrorType.ExceptionInDedicatedErrorPage).GetCookies();
134+
135+
// Act
136+
var response = await GetTestResponseAsync("/Errors/500", cookies);
137+
138+
// Assert
139+
Validator.Redirect("500")(response);
140+
Assert.Equal("/Errors/500?aspxerrorpath=/Errors/500", response.LocationHeader);
141+
}
142+
143+
/// <summary>
144+
/// Verify behavior when the pretty HTTP 500 page fails itself.
145+
/// </summary>
146+
[Fact]
147+
[Priority(2)]
148+
[Category("P2Tests")]
149+
public async Task ErrorInErrorPageWithPathToSelf()
150+
{
151+
// Arrange
152+
var cookies = new SimulatedErrorRequest(EndpointType.Pages, SimulatedErrorType.ExceptionInDedicatedErrorPage).GetCookies();
153+
154+
// Act
155+
var response = await GetTestResponseAsync("/Errors/500?aspxerrorpath=/Errors/500", cookies);
156+
157+
// Assert
158+
Validator.SimpleHtml(HttpStatusCode.InternalServerError)(response);
159+
Assert.Contains(
160+
"An exception occurred while processing your request. " +
161+
"Additionally, another exception occurred while executing the custom error page for the first exception. " +
162+
"The request has been terminated.",
163+
response.Content);
164+
}
165+
96166
/// <summary>
97167
/// Simulate cases where application code throws different sorts of exceptions.
98168
/// </summary>
@@ -106,7 +176,7 @@ public async Task SimulateError(EndpointType endpointType, SimulatedErrorType si
106176
var request = new SimulatedErrorRequest(endpointType, simulatedErrorType);
107177

108178
// Act
109-
var response = await GetTestResponseAsync(request.GetRelativePath());
179+
var response = await GetTestResponseAsync(request);
110180

111181
// Assert
112182
if (!ExpectedSimulatedErrorResponses.TryGetValue(request, out var validator))
@@ -153,7 +223,13 @@ public static IEnumerable<object[]> AllTestData
153223
{ SER(EndpointType.Api, SimulatedErrorType.Result404), Validator.PrettyHtml(HttpStatusCode.NotFound) },
154224
{ SER(EndpointType.Api, SimulatedErrorType.Result503), Validator.SimpleHtml(HttpStatusCode.ServiceUnavailable, SimulatedErrorType.Result503) },
155225
{ SER(EndpointType.Api, SimulatedErrorType.UserSafeException), Validator.SimpleHtml(HttpStatusCode.InternalServerError, SimulatedErrorType.UserSafeException) },
226+
{ SER(EndpointType.Api, SimulatedErrorType.ExceptionInView), Validator.SimpleHtml(HttpStatusCode.InternalServerError, SimulatedErrorType.ExceptionInView) },
227+
{ SER(EndpointType.Api, SimulatedErrorType.ExceptionInInlineErrorPage), Validator.SimpleHtml(HttpStatusCode.InternalServerError, SimulatedErrorType.ExceptionInInlineErrorPage) },
228+
{ SER(EndpointType.Api, SimulatedErrorType.ExceptionInDedicatedErrorPage), Validator.SimpleHtml(HttpStatusCode.InternalServerError, SimulatedErrorType.ExceptionInDedicatedErrorPage) },
156229
{ SER(EndpointType.OData, SimulatedErrorType.Exception), Validator.Xml() },
230+
{ SER(EndpointType.OData, SimulatedErrorType.ExceptionInView), Validator.Xml() },
231+
{ SER(EndpointType.OData, SimulatedErrorType.ExceptionInInlineErrorPage), Validator.Xml() },
232+
{ SER(EndpointType.OData, SimulatedErrorType.ExceptionInDedicatedErrorPage), Validator.Xml() },
157233
{ SER(EndpointType.OData, SimulatedErrorType.HttpException400), Validator.Xml() },
158234
{ SER(EndpointType.OData, SimulatedErrorType.HttpException404), Validator.Xml() },
159235
{ SER(EndpointType.OData, SimulatedErrorType.HttpException500), Validator.Xml() },
@@ -175,6 +251,7 @@ public static IEnumerable<object[]> AllTestData
175251
{ SER(EndpointType.Pages, SimulatedErrorType.Result400), Validator.SimpleHtml(HttpStatusCode.BadRequest, SimulatedErrorType.Result400) },
176252
{ SER(EndpointType.Pages, SimulatedErrorType.Result404), Validator.PrettyHtml(HttpStatusCode.NotFound) },
177253
{ SER(EndpointType.Pages, SimulatedErrorType.Result503), Validator.SimpleHtml(HttpStatusCode.ServiceUnavailable, SimulatedErrorType.Result503) },
254+
{ SER(EndpointType.Pages, SimulatedErrorType.ExceptionInInlineErrorPage), Validator.Redirect("500") },
178255
};
179256

180257
/// <summary>
@@ -198,14 +275,31 @@ private async Task<TestResponse> GetTestResponseAsync(string relativePath, HttpR
198275
}
199276
}
200277

201-
private async Task<TestResponse> GetTestResponseAsync(string relativePath)
278+
private async Task<TestResponse> GetTestResponseAsync(SimulatedErrorRequest errorRequest)
279+
{
280+
return await GetTestResponseAsync(
281+
errorRequest.GetRelativePath(),
282+
errorRequest.GetCookies());
283+
}
284+
285+
private async Task<TestResponse> GetTestResponseAsync(string relativePath, IReadOnlyDictionary<string, string> cookies)
202286
{
203287
using (var request = new HttpRequestMessage(HttpMethod.Get, GetRequestUri(relativePath)))
204288
{
289+
foreach (var cookie in cookies)
290+
{
291+
_cookieContainer.Add(new Uri(UrlHelper.BaseUrl), new Cookie { Name = cookie.Key, Value = cookie.Value });
292+
}
293+
205294
return await GetTestResponseAsync(relativePath, request);
206295
}
207296
}
208297

298+
private async Task<TestResponse> GetTestResponseAsync(string relativePath)
299+
{
300+
return await GetTestResponseAsync(relativePath, new Dictionary<string, string>());
301+
}
302+
209303
private Uri GetRequestUri(string relativePath)
210304
{
211305
return new Uri(new Uri(UrlHelper.BaseUrl), relativePath);

0 commit comments

Comments
 (0)