Skip to content

Commit

Permalink
Added tests and fix for view location error.
Browse files Browse the repository at this point in the history
Processors are now no longer wrapped in a safe execution method
so exceptions are bubbled up. Returning null from a propcessor
will execute the next one, so if anything goes wrong when processing
in your processor.. catch it, log it, return null :-)
  • Loading branch information
grumpydev committed Oct 1, 2012
1 parent df74331 commit a9c21b4
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 31 deletions.
123 changes: 107 additions & 16 deletions src/Nancy.Tests.Functional/Tests/ContentNegotiationFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ namespace Nancy.Tests.Functional.Tests
using System.Collections.Generic;
using System.IO;

using Nancy.ErrorHandling;
using Nancy.IO;
using Nancy.Responses.Negotiation;
using Nancy.Testing;
Expand Down Expand Up @@ -372,22 +373,6 @@ public void Should_add_link_header_for_matching_response_processors()
Assert.True(response.Headers["Link"].Contains(@"</.xml>; rel=""application/xml"""));
}

private static Func<dynamic, dynamic> CreateNegotiatedResponse(Action<Negotiator> action = null)
{
var context =
new NancyContext { NegotiationContext = new NegotiationContext() };

var negotiator =
new Negotiator(context);

if (action != null)
{
action.Invoke(negotiator);
}

return parameters => negotiator;
}

[Fact]
public void Should_set_negotiated_status_code_to_response_when_set_as_integer()
{
Expand Down Expand Up @@ -442,6 +427,77 @@ public void Should_set_negotiated_status_code_to_response_when_set_as_httpstatus
Assert.Equal(HttpStatusCode.InsufficientStorage, response.StatusCode);
}

[Fact]
public void Should_throw_exception_if_view_location_fails()
{
var browser = new Browser(with =>
{
with.ResponseProcessor<ViewProcessor>();
with.Module(new FakeModuleInvalidViewName());
});

// When
var result = Record.Exception(() =>
{
var response = browser.Get(
"/FakeModuleInvalidViewName",
with =>
{ with.Accept("text/html", 1.0m); });
});

// Then
Assert.NotNull(result);
Assert.Contains("Unable to locate view", result.ToString());
}

[Fact]
public void Should_use_next_processor_if_processor_returns_null()
{
// Given
var browser = new Browser(with =>
{
with.ResponseProcessors(typeof(NullProcessor), typeof(TestProcessor));
with.Module(new ConfigurableNancyModule(x =>
{
x.Get("/test", CreateNegotiatedResponse(config =>
{
config.WithAllowedMediaRange("application/xml");
}));
}));
});

// When
var response = browser.Get("/test", with =>
{
with.Accept("application/xml", 0.9m);
});

// Then
var bodyResult = response.Body.AsString();
Assert.True(bodyResult.StartsWith("application/xml"), string.Format("Body should have started with 'application/xml' but was actually '{0}'", bodyResult));
}

private static Func<dynamic, dynamic> CreateNegotiatedResponse(Action<Negotiator> action = null)
{
var context =
new NancyContext { NegotiationContext = new NegotiationContext() };

var negotiator =
new Negotiator(context);

if (action != null)
{
action.Invoke(negotiator);
}

return parameters =>
{
return negotiator;
};
}

/// <summary>
/// Test response processor that will accept any type
/// and put the content type and model type into the
Expand Down Expand Up @@ -475,6 +531,33 @@ public Response Process(MediaRange requestedMediaRange, dynamic model, NancyCont
}
}

public class NullProcessor : IResponseProcessor
{
private const string ResponseTemplate = "{0}\n{1}";

public IEnumerable<Tuple<string, MediaRange>> ExtensionMappings
{
get
{
yield break;
}
}

public ProcessorMatch CanProcess(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
return new ProcessorMatch
{
RequestedContentTypeResult = MatchResult.ExactMatch,
ModelResult = MatchResult.ExactMatch
};
}

public Response Process(MediaRange requestedMediaRange, dynamic model, NancyContext context)
{
return null;
}
}

public class ModelProcessor : IResponseProcessor
{
private const string ResponseTemplate = "{0}\n{1}";
Expand All @@ -501,5 +584,13 @@ public Response Process(MediaRange requestedMediaRange, dynamic model, NancyCont
return (string) model;
}
}

public class FakeModuleInvalidViewName : NancyModule
{
public FakeModuleInvalidViewName()
{
Get["/FakeModuleInvalidViewName"] = _ => View["blahblahblah"];
}
}
}
}
16 changes: 1 addition & 15 deletions src/Nancy/Routing/DefaultRouteInvoker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ private static Response NegotiateResponse(IEnumerable<Tuple<string, IEnumerable<
context.WriteTraceLog(sb => sb.AppendFormat("[DefaultRouteInvoker] Invoking processor: {0}\n", processorType));

var response =
SafeInvokeResponseProcessor(prioritizedProcessor.Item1, compatibleHeader.Item1, negotiator.NegotiationContext.GetModelForMediaRange(compatibleHeader.Item1), context);
prioritizedProcessor.Item1.Process(compatibleHeader.Item1, negotiator.NegotiationContext.GetModelForMediaRange(compatibleHeader.Item1), context);

if (response != null)
{
Expand Down Expand Up @@ -247,20 +247,6 @@ private IEnumerable<Tuple<string, decimal>> GetCoercedAcceptHeaders(NancyContext
return currentHeaders;
}

private static Response SafeInvokeResponseProcessor(IResponseProcessor responseProcessor, MediaRange mediaRange, object model, NancyContext context)
{
try
{
return responseProcessor.Process(mediaRange, model, context);
}
catch (Exception e)
{
context.WriteTraceLog(sb => sb.AppendFormat("[DefaultRouteInvoker] Processor threw {0} exception: {1}", e.GetType(), e.Message));
}

return null;
}

private static Negotiator GetNegotiator(object routeResult, NancyContext context)
{
var negotiator = routeResult as Negotiator;
Expand Down

0 comments on commit a9c21b4

Please sign in to comment.