Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
using SoapCore.Extensibility;

namespace SoapCore.Tests.DuplicateFault
{
public class CountingFaultExceptionTransformer : IFaultExceptionTransformer
{
private int _provideFaultCallCount;

public int ProvideFaultCallCount => _provideFaultCallCount;

public Message ProvideFault(Exception exception, MessageVersion messageVersion, Message requestMessage, ConcurrentXmlNamespaceLookup xmlNamespaceLookup)
{
_provideFaultCallCount++;

var faultException = new FaultException(new FaultReason(exception.Message), new FaultCode("Sender"), null);
var messageFault = faultException.CreateMessageFault();
var bodyWriter = new MessageFaultBodyWriter(messageFault, messageVersion);

return Message.CreateMessage(messageVersion, null, bodyWriter);
}
}
}
71 changes: 71 additions & 0 deletions src/SoapCore.Tests/DuplicateFault/CountingLoggerProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Logging;

namespace SoapCore.Tests.DuplicateFault
{
public sealed class CountingLoggerProvider : ILoggerProvider
{
private readonly List<string> _errors = new List<string>();

public IReadOnlyList<string> Errors
{
get
{
lock (_errors)
{
return _errors.ToList();
}
}
}

public ILogger CreateLogger(string categoryName)
{
return new CountingLogger(this);
}

public void Dispose()
{
}

private void AddError(string message)
{
lock (_errors)
{
_errors.Add(message);
}
}

private sealed class CountingLogger : ILogger
{
private readonly CountingLoggerProvider _provider;

public CountingLogger(CountingLoggerProvider provider)
{
_provider = provider;
}

public IDisposable BeginScope<TState>(TState state)
where TState : notnull
{
return null;
}

public bool IsEnabled(LogLevel logLevel)
{
return true;
}

public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
{
if (logLevel != LogLevel.Error)
{
return;
}

_provider.AddError(formatter(state, exception));
}
}
}
}
132 changes: 132 additions & 0 deletions src/SoapCore.Tests/DuplicateFault/DuplicateFaultTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace SoapCore.Tests.DuplicateFault
{
// https://github.com/DigDes/SoapCore/issues/1192
[TestClass]
public class DuplicateFaultTests
{
private const string FaultMessage = "Test fault message";
private const string ErrorLogMessage = "An error occurred processing the message";

[TestMethod]
public async Task ProvideFaultIsCalledOnce()
{
using (var host = CreateTestHost())
{
await PostFaultingRequestAsync(host);

var faultExceptionTransformer = host.Services.GetRequiredService<CountingFaultExceptionTransformer>();

Assert.AreEqual(1, faultExceptionTransformer.ProvideFaultCallCount);
}
}

[TestMethod]
public async Task ErrorIsLoggedOnce()
{
using (var host = CreateTestHost())
{
await PostFaultingRequestAsync(host);

var loggerProvider = host.Services.GetRequiredService<CountingLoggerProvider>();

Assert.AreEqual(1, loggerProvider.Errors.Count(error => error == ErrorLogMessage));
}
}

[TestMethod]
public async Task BeforeSendReplyIsCalledOnce()
{
using (var host = CreateTestHost())
{
await PostFaultingRequestAsync(host);

var messageInspector = host.Services.GetRequiredService<StampingMessageInspector>();

Assert.AreEqual(1, messageInspector.AfterReceiveRequestCallCount);
Assert.AreEqual(1, messageInspector.BeforeSendReplyCallCount);
}
}

[TestMethod]
public async Task BeforeSendReplyIsCalledOnceWhenTheResponseFilterThrows()
{
using (var host = CreateTestHost(registerThrowingResponseFilter: true))
{
await PostPingRequestAsync(host);

var messageInspector = host.Services.GetRequiredService<StampingMessageInspector>();

Assert.AreEqual(1, messageInspector.AfterReceiveRequestCallCount);
Assert.AreEqual(1, messageInspector.BeforeSendReplyCallCount);
}
}

[TestMethod]
public async Task MessageHandedToBeforeSendReplyIsTheMessageThatIsSent()
{
using (var host = CreateTestHost())
{
var response = await PostFaultingRequestAsync(host);

Assert.IsTrue(response.Contains(FaultMessage), "The fault should carry the reason of the exception");
Assert.IsTrue(response.Contains(StampingMessageInspector.HeaderName), "The fault should carry the header added by BeforeSendReply");
Assert.IsTrue(response.Contains(StampingMessageInspector.HeaderValue), "The fault should carry the value written by BeforeSendReply");
}
}

private TestServer CreateTestHost(bool registerThrowingResponseFilter = false)
{
var webHostBuilder = new WebHostBuilder()
.UseStartup<Startup>()
.UseSetting(Startup.ThrowingResponseFilterSetting, registerThrowingResponseFilter.ToString());

return new TestServer(webHostBuilder);
}

private Task<string> PostFaultingRequestAsync(TestServer host)
{
var bodyContent = $@"<ThrowExceptionWithMessage xmlns=""http://tempuri.org/"">
<message>{FaultMessage}</message>
</ThrowExceptionWithMessage>";

return PostAsync(host, "ThrowExceptionWithMessage", bodyContent);
}

private Task<string> PostPingRequestAsync(TestServer host)
{
var bodyContent = @"<Ping xmlns=""http://tempuri.org/"">
<s>Hello World</s>
</Ping>";

return PostAsync(host, "Ping", bodyContent);
}

private async Task<string> PostAsync(TestServer host, string soapAction, string bodyContent)
{
var body = $@"<soapenv:Envelope xmlns:soapenv=""http://schemas.xmlsoap.org/soap/envelope/"">
<soapenv:Body>
{bodyContent}
</soapenv:Body>
</soapenv:Envelope>
";

using (var content = new StringContent(body, Encoding.UTF8, "text/xml"))
using (var res = await host.CreateRequest("/Service.svc").AddHeader("SOAPAction", $@"""{soapAction}""").And(msg => msg.Content = content).PostAsync())
{
Assert.AreEqual(HttpStatusCode.InternalServerError, res.StatusCode);

return await res.Content.ReadAsStringAsync();
}
}
}
}
34 changes: 34 additions & 0 deletions src/SoapCore.Tests/DuplicateFault/StampingMessageInspector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.ServiceModel.Channels;
using SoapCore.Extensibility;
using SoapCore.ServiceModel;

namespace SoapCore.Tests.DuplicateFault
{
public class StampingMessageInspector : IMessageInspector2
{
public const string HeaderName = "InspectorStamp";
public const string HeaderNamespace = "urn:soapcore-tests";
public const string HeaderValue = "was-here";

private int _afterReceiveRequestCallCount;
private int _beforeSendReplyCallCount;

public int AfterReceiveRequestCallCount => _afterReceiveRequestCallCount;

public int BeforeSendReplyCallCount => _beforeSendReplyCallCount;

public object AfterReceiveRequest(ref Message message, ServiceDescription serviceDescription)
{
_afterReceiveRequestCallCount++;

return null;
}

public void BeforeSendReply(ref Message reply, ServiceDescription serviceDescription, object correlationState)
{
_beforeSendReplyCallCount++;

reply.Headers.Add(MessageHeader.CreateHeader(HeaderName, HeaderNamespace, HeaderValue));
}
}
}
68 changes: 68 additions & 0 deletions src/SoapCore.Tests/DuplicateFault/Startup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using SoapCore.Extensibility;

namespace SoapCore.Tests.DuplicateFault
{
public class Startup
{
public const string ThrowingResponseFilterSetting = "RegisterThrowingResponseFilter";

public Startup(IConfiguration configuration)
{
Configuration = configuration;
RegisterThrowingResponseFilter = configuration.GetValue<bool>(ThrowingResponseFilterSetting);
}

public IConfiguration Configuration { get; }

public bool RegisterThrowingResponseFilter { get; }

public void ConfigureServices(IServiceCollection services)
{
var loggerProvider = new CountingLoggerProvider();
var faultExceptionTransformer = new CountingFaultExceptionTransformer();
var messageInspector = new StampingMessageInspector();

services.AddSingleton(loggerProvider);
services.AddLogging(logging => logging.AddProvider(loggerProvider));

services.AddSoapCore();
services.TryAddSingleton<TestService>();

services.AddSingleton(faultExceptionTransformer);
services.AddSingleton<IFaultExceptionTransformer>(faultExceptionTransformer);

services.AddSingleton(messageInspector);
services.AddSoapMessageInspector(messageInspector);

if (RegisterThrowingResponseFilter)
{
services.AddSoapMessageFilter(new ThrowingResponseFilter());
}

services.AddRouting();
}

#if !NETCOREAPP3_0_OR_GREATER
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseSoapEndpoint<TestService>("/Service.svc", new SoapEncoderOptions(), SoapSerializer.DataContractSerializer);
}
#else
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
app.UseRouting();

app.UseEndpoints(x =>
{
x.UseSoapEndpoint<TestService>("/Service.svc", new SoapEncoderOptions(), SoapSerializer.DataContractSerializer);
});
}
#endif
}
}
20 changes: 20 additions & 0 deletions src/SoapCore.Tests/DuplicateFault/ThrowingResponseFilter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using System.ServiceModel.Channels;
using System.Threading.Tasks;
using SoapCore.Extensibility;

namespace SoapCore.Tests.DuplicateFault
{
public class ThrowingResponseFilter : IAsyncMessageFilter
{
public Task OnRequestExecuting(Message message)
{
return Task.CompletedTask;
}

public Task OnResponseExecuting(Message message)
{
throw new InvalidOperationException("The response is not acceptable.");
}
}
}
Loading
Loading