1026 lines
42 KiB
C#
1026 lines
42 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.AspNetCore.Builder;
|
|
using Microsoft.AspNetCore.Hosting;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Http.Features;
|
|
using Microsoft.AspNetCore.TestHost;
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using Microsoft.Extensions.Internal;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using Microsoft.Extensions.Logging.Testing;
|
|
using Microsoft.Extensions.Options;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Session
|
|
{
|
|
public class SessionTests
|
|
{
|
|
[Fact]
|
|
public async Task ReadingEmptySessionDoesNotCreateCookie()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
|
|
app.Run(context =>
|
|
{
|
|
Assert.Null(context.Session.GetString("NotFound"));
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.False(response.Headers.TryGetValues("Set-Cookie", out var _));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SettingAValueCausesTheCookieToBeCreated()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
Assert.Null(context.Session.GetString("Key"));
|
|
context.Session.SetString("Key", "Value");
|
|
Assert.Equal("Value", context.Session.GetString("Key"));
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.True(response.Headers.TryGetValues("Set-Cookie", out var values));
|
|
Assert.Single(values);
|
|
Assert.True(!string.IsNullOrWhiteSpace(values.First()));
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(CookieSecurePolicy.Always, "http://example.com/testpath", true)]
|
|
[InlineData(CookieSecurePolicy.Always, "https://example.com/testpath", true)]
|
|
[InlineData(CookieSecurePolicy.None, "http://example.com/testpath", false)]
|
|
[InlineData(CookieSecurePolicy.None, "https://example.com/testpath", false)]
|
|
[InlineData(CookieSecurePolicy.SameAsRequest, "http://example.com/testpath", false)]
|
|
[InlineData(CookieSecurePolicy.SameAsRequest, "https://example.com/testpath", true)]
|
|
public async Task SecureSessionBasedOnHttpsAndSecurePolicy(
|
|
CookieSecurePolicy cookieSecurePolicy,
|
|
string requestUri,
|
|
bool shouldBeSecureOnly)
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession(new SessionOptions
|
|
{
|
|
Cookie =
|
|
{
|
|
Name = "TestCookie",
|
|
SecurePolicy = cookieSecurePolicy
|
|
}
|
|
});
|
|
app.Run(context =>
|
|
{
|
|
Assert.Null(context.Session.GetString("Key"));
|
|
context.Session.SetString("Key", "Value");
|
|
Assert.Equal("Value", context.Session.GetString("Key"));
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(requestUri);
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.True(response.Headers.TryGetValues("Set-Cookie", out var values));
|
|
Assert.Single(values);
|
|
if (shouldBeSecureOnly)
|
|
{
|
|
Assert.Contains("; secure", values.First());
|
|
}
|
|
else
|
|
{
|
|
Assert.DoesNotContain("; secure", values.First());
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionCanBeAccessedOnTheNextRequest()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
int? value = context.Session.GetInt32("Key");
|
|
if (context.Request.Path == new PathString("/first"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 0;
|
|
}
|
|
Assert.True(value.HasValue);
|
|
context.Session.SetInt32("Key", value.Value + 1);
|
|
return context.Response.WriteAsync(value.Value.ToString());
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync("first");
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
|
|
|
client = server.CreateClient();
|
|
var cookie = SetCookieHeaderValue.ParseList(response.Headers.GetValues("Set-Cookie").ToList()).First();
|
|
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
|
Assert.Equal("1", await client.GetStringAsync("/"));
|
|
Assert.Equal("2", await client.GetStringAsync("/"));
|
|
Assert.Equal("3", await client.GetStringAsync("/"));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RemovedItemCannotBeAccessedAgain()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
int? value = context.Session.GetInt32("Key");
|
|
if (context.Request.Path == new PathString("/first"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 0;
|
|
context.Session.SetInt32("Key", 1);
|
|
}
|
|
else if (context.Request.Path == new PathString("/second"))
|
|
{
|
|
Assert.True(value.HasValue);
|
|
Assert.Equal(1, value);
|
|
context.Session.Remove("Key");
|
|
}
|
|
else if (context.Request.Path == new PathString("/third"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 2;
|
|
}
|
|
return context.Response.WriteAsync(value.Value.ToString());
|
|
});
|
|
})
|
|
.ConfigureServices(
|
|
services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync("first");
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
|
|
|
client = server.CreateClient();
|
|
var cookie = SetCookieHeaderValue.ParseList(response.Headers.GetValues("Set-Cookie").ToList()).First();
|
|
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
|
Assert.Equal("1", await client.GetStringAsync("/second"));
|
|
Assert.Equal("2", await client.GetStringAsync("/third"));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ClearedItemsCannotBeAccessedAgain()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
int? value = context.Session.GetInt32("Key");
|
|
if (context.Request.Path == new PathString("/first"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 0;
|
|
context.Session.SetInt32("Key", 1);
|
|
}
|
|
else if (context.Request.Path == new PathString("/second"))
|
|
{
|
|
Assert.True(value.HasValue);
|
|
Assert.Equal(1, value);
|
|
context.Session.Clear();
|
|
}
|
|
else if (context.Request.Path == new PathString("/third"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 2;
|
|
}
|
|
return context.Response.WriteAsync(value.Value.ToString());
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync("first");
|
|
response.EnsureSuccessStatusCode();
|
|
Assert.Equal("0", await response.Content.ReadAsStringAsync());
|
|
|
|
client = server.CreateClient();
|
|
var cookie = SetCookieHeaderValue.ParseList(response.Headers.GetValues("Set-Cookie").ToList()).First();
|
|
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
|
Assert.Equal("1", await client.GetStringAsync("/second"));
|
|
Assert.Equal("2", await client.GetStringAsync("/third"));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionStart_LogsInformation()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
context.Session.SetString("Key", "Value");
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Equal(2, sessionLogMessages.Count);
|
|
Assert.Contains("started", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
|
|
Assert.Contains("stored", sessionLogMessages[1].State.ToString());
|
|
Assert.Equal(LogLevel.Debug, sessionLogMessages[1].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task ExpiredSession_LogsInfo()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
int? value = context.Session.GetInt32("Key");
|
|
if (context.Request.Path == new PathString("/first"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 1;
|
|
context.Session.SetInt32("Key", 1);
|
|
}
|
|
else if (context.Request.Path == new PathString("/second"))
|
|
{
|
|
Assert.False(value.HasValue);
|
|
value = 2;
|
|
}
|
|
return context.Response.WriteAsync(value.Value.ToString());
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMilliseconds(30));
|
|
});
|
|
|
|
string result;
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync("first");
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
client = server.CreateClient();
|
|
var cookie = SetCookieHeaderValue.ParseList(response.Headers.GetValues("Set-Cookie").ToList()).First();
|
|
client.DefaultRequestHeaders.Add("Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
|
Thread.Sleep(50);
|
|
result = await client.GetStringAsync("/second");
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Equal("2", result);
|
|
Assert.Equal(3, sessionLogMessages.Count);
|
|
Assert.Contains("started", sessionLogMessages[0].State.ToString());
|
|
Assert.Contains("stored", sessionLogMessages[1].State.ToString());
|
|
Assert.Contains("expired", sessionLogMessages[2].State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
|
|
Assert.Equal(LogLevel.Debug, sessionLogMessages[1].LogLevel);
|
|
Assert.Equal(LogLevel.Information, sessionLogMessages[2].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RefreshesSession_WhenSessionData_IsNotModified()
|
|
{
|
|
var clock = new TestClock();
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
string responseData = string.Empty;
|
|
if (context.Request.Path == new PathString("/AddDataToSession"))
|
|
{
|
|
context.Session.SetInt32("Key", 10);
|
|
responseData = "added data to session";
|
|
}
|
|
else if (context.Request.Path == new PathString("/AccessSessionData"))
|
|
{
|
|
var value = context.Session.GetInt32("Key");
|
|
responseData = (value == null) ? "No value found in session." : value.ToString();
|
|
}
|
|
else if (context.Request.Path == new PathString("/DoNotAccessSessionData"))
|
|
{
|
|
responseData = "did not access session data";
|
|
}
|
|
|
|
return context.Response.WriteAsync(responseData);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), NullLoggerFactory.Instance);
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession(o => o.IdleTimeout = TimeSpan.FromMinutes(20));
|
|
services.Configure<MemoryCacheOptions>(o => o.Clock = clock);
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync("AddDataToSession");
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
client = server.CreateClient();
|
|
var cookie = SetCookieHeaderValue.ParseList(response.Headers.GetValues("Set-Cookie").ToList()).First();
|
|
client.DefaultRequestHeaders.Add(
|
|
"Cookie", new CookieHeaderValue(cookie.Name, cookie.Value).ToString());
|
|
|
|
for (var i = 0; i < 5; i++)
|
|
{
|
|
clock.Add(TimeSpan.FromMinutes(10));
|
|
await client.GetStringAsync("/DoNotAccessSessionData");
|
|
}
|
|
|
|
var data = await client.GetStringAsync("/AccessSessionData");
|
|
Assert.Equal("10", data);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionFeature_IsUnregistered_WhenResponseGoingOut()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.Use(async (httpContext, next) =>
|
|
{
|
|
await next();
|
|
|
|
Assert.Null(httpContext.Features.Get<ISessionFeature>());
|
|
});
|
|
|
|
app.UseSession();
|
|
|
|
app.Run(context =>
|
|
{
|
|
context.Session.SetString("key", "value");
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionFeature_IsUnregistered_WhenResponseGoingOut_AndAnUnhandledExcetionIsThrown()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.Use(async (httpContext, next) =>
|
|
{
|
|
var exceptionThrown = false;
|
|
try
|
|
{
|
|
await next();
|
|
}
|
|
catch
|
|
{
|
|
exceptionThrown = true;
|
|
}
|
|
|
|
Assert.True(exceptionThrown);
|
|
Assert.Null(httpContext.Features.Get<ISessionFeature>());
|
|
});
|
|
|
|
app.UseSession();
|
|
|
|
app.Run(context =>
|
|
{
|
|
throw new InvalidOperationException("An error occurred.");
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionKeys_AreCaseSensitive()
|
|
{
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
context.Session.SetString("KEY", "VALUE");
|
|
context.Session.SetString("key", "value");
|
|
Assert.Equal("VALUE", context.Session.GetString("KEY"));
|
|
Assert.Equal("value", context.Session.GetString("key"));
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddDistributedMemoryCache();
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheReadException()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
Assert.False(context.Session.TryGetValue("key", out var value));
|
|
Assert.Null(value);
|
|
Assert.Equal(string.Empty, context.Session.Id);
|
|
Assert.False(context.Session.Keys.Any());
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DisableGet = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Single(sessionLogMessages);
|
|
Assert.Contains("Session cache read exception", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheLoadAsyncException()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(async context =>
|
|
{
|
|
await Assert.ThrowsAsync<InvalidOperationException>(() => context.Session.LoadAsync());
|
|
Assert.False(context.Session.IsAvailable);
|
|
Assert.Equal(string.Empty, context.Session.Id);
|
|
Assert.False(context.Session.Keys.Any());
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DisableGet = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Single(sessionLogMessages);
|
|
Assert.Contains("Session cache read exception", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheLoadAsyncTimeoutException()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession(new SessionOptions()
|
|
{
|
|
IOTimeout = TimeSpan.FromSeconds(0.5)
|
|
});
|
|
app.Run(async context =>
|
|
{
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Session.LoadAsync());
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DelayGetAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Single(sessionLogMessages);
|
|
Assert.Contains("Loading the session timed out.", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Warning, sessionLogMessages[0].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLoadAsyncCanceledException()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<DistributedSession>,
|
|
TestSink.EnableWithTypeName<DistributedSession>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(async context =>
|
|
{
|
|
var cts = new CancellationTokenSource();
|
|
var token = cts.Token;
|
|
cts.Cancel();
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Session.LoadAsync(token));
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DelayGetAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
Assert.Empty(sessionLogMessages);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheCommitException()
|
|
{
|
|
var sink = new TestSink(
|
|
writeContext =>
|
|
{
|
|
return writeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| writeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
},
|
|
beginScopeContext =>
|
|
{
|
|
return beginScopeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| beginScopeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
});
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
context.Session.SetInt32("key", 0);
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DisableSetAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessage = sink.Writes.Where(message => message.LoggerName.Equals(typeof(DistributedSession).FullName, StringComparison.Ordinal)).Single();
|
|
|
|
Assert.Contains("Session started", sessionLogMessage.State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionLogMessage.LogLevel);
|
|
|
|
var sessionMiddlewareLogMessage = sink.Writes.Where(message => message.LoggerName.Equals(typeof(SessionMiddleware).FullName, StringComparison.Ordinal)).Single();
|
|
|
|
Assert.Contains("Error closing the session.", sessionMiddlewareLogMessage.State.ToString());
|
|
Assert.Equal(LogLevel.Error, sessionMiddlewareLogMessage.LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheCommitTimeoutException()
|
|
{
|
|
var sink = new TestSink(
|
|
writeContext =>
|
|
{
|
|
return writeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| writeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
},
|
|
beginScopeContext =>
|
|
{
|
|
return beginScopeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| beginScopeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
});
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession(new SessionOptions()
|
|
{
|
|
IOTimeout = TimeSpan.FromSeconds(0.5)
|
|
});
|
|
app.Run(context =>
|
|
{
|
|
context.Session.SetInt32("key", 0);
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DelaySetAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes.Where(message => message.LoggerName.Equals(typeof(DistributedSession).FullName, StringComparison.Ordinal)).ToList();
|
|
|
|
Assert.Contains("Session started", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionLogMessages[0].LogLevel);
|
|
|
|
Assert.Contains("Committing the session timed out.", sessionLogMessages[1].State.ToString());
|
|
Assert.Equal(LogLevel.Warning, sessionLogMessages[1].LogLevel);
|
|
|
|
var sessionMiddlewareLogs = sink.Writes.Where(message => message.LoggerName.Equals(typeof(SessionMiddleware).FullName, StringComparison.Ordinal)).ToList();
|
|
|
|
Assert.Contains("Committing the session was canceled.", sessionMiddlewareLogs[0].State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionMiddlewareLogs[0].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheCommitCanceledException()
|
|
{
|
|
var sink = new TestSink(
|
|
writeContext =>
|
|
{
|
|
return writeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| writeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
},
|
|
beginScopeContext =>
|
|
{
|
|
return beginScopeContext.LoggerName.Equals(typeof(SessionMiddleware).FullName)
|
|
|| beginScopeContext.LoggerName.Equals(typeof(DistributedSession).FullName);
|
|
});
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(async context =>
|
|
{
|
|
context.Session.SetInt32("key", 0);
|
|
var cts = new CancellationTokenSource();
|
|
var token = cts.Token;
|
|
cts.Cancel();
|
|
await Assert.ThrowsAsync<OperationCanceledException>(() => context.Session.CommitAsync(token));
|
|
context.RequestAborted = token;
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DelaySetAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
Assert.Empty(sink.Writes.Where(message => message.LoggerName.Equals(typeof(DistributedSession).FullName, StringComparison.Ordinal)));
|
|
|
|
var sessionMiddlewareLogs = sink.Writes.Where(message => message.LoggerName.Equals(typeof(SessionMiddleware).FullName, StringComparison.Ordinal)).ToList();
|
|
|
|
Assert.Contains("Committing the session was canceled.", sessionMiddlewareLogs[0].State.ToString());
|
|
Assert.Equal(LogLevel.Information, sessionMiddlewareLogs[0].LogLevel);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task SessionLogsCacheRefreshException()
|
|
{
|
|
var sink = new TestSink(
|
|
TestSink.EnableWithTypeName<SessionMiddleware>,
|
|
TestSink.EnableWithTypeName<SessionMiddleware>);
|
|
var loggerFactory = new TestLoggerFactory(sink, enabled: true);
|
|
var builder = new WebHostBuilder()
|
|
.Configure(app =>
|
|
{
|
|
app.UseSession();
|
|
app.Run(context =>
|
|
{
|
|
// The middleware calls context.Session.CommitAsync() once per request
|
|
return Task.FromResult(0);
|
|
});
|
|
})
|
|
.ConfigureServices(services =>
|
|
{
|
|
services.AddSingleton(typeof(ILoggerFactory), loggerFactory);
|
|
services.AddSingleton<IDistributedCache>(new UnreliableCache(new MemoryCache(new MemoryCacheOptions()))
|
|
{
|
|
DisableRefreshAsync = true
|
|
});
|
|
services.AddSession();
|
|
});
|
|
|
|
using (var server = new TestServer(builder))
|
|
{
|
|
var client = server.CreateClient();
|
|
var response = await client.GetAsync(string.Empty);
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
var sessionLogMessages = sink.Writes;
|
|
|
|
Assert.Single(sessionLogMessages);
|
|
Assert.Contains("Error closing the session.", sessionLogMessages[0].State.ToString());
|
|
Assert.Equal(LogLevel.Error, sessionLogMessages[0].LogLevel);
|
|
}
|
|
|
|
private class TestClock : ISystemClock
|
|
{
|
|
public TestClock()
|
|
{
|
|
UtcNow = new DateTimeOffset(2013, 1, 1, 1, 0, 0, TimeSpan.Zero);
|
|
}
|
|
|
|
public DateTimeOffset UtcNow { get; private set; }
|
|
|
|
public void Add(TimeSpan timespan)
|
|
{
|
|
UtcNow = UtcNow.Add(timespan);
|
|
}
|
|
}
|
|
|
|
private class UnreliableCache : IDistributedCache
|
|
{
|
|
private readonly MemoryDistributedCache _cache;
|
|
|
|
public bool DisableGet { get; set; }
|
|
public bool DisableSetAsync { get; set; }
|
|
public bool DisableRefreshAsync { get; set; }
|
|
public bool DelayGetAsync { get; set; }
|
|
public bool DelaySetAsync { get; set; }
|
|
public bool DelayRefreshAsync { get; set; }
|
|
|
|
public UnreliableCache(IMemoryCache memoryCache)
|
|
{
|
|
_cache = new MemoryDistributedCache(Options.Create(new MemoryDistributedCacheOptions()));
|
|
}
|
|
|
|
public byte[] Get(string key)
|
|
{
|
|
if (DisableGet)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
return _cache.Get(key);
|
|
}
|
|
|
|
public Task<byte[]> GetAsync(string key, CancellationToken token = default)
|
|
{
|
|
if (DisableGet)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
if (DelayGetAsync)
|
|
{
|
|
token.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
|
|
token.ThrowIfCancellationRequested();
|
|
}
|
|
return _cache.GetAsync(key, token);
|
|
}
|
|
|
|
public void Refresh(string key) => _cache.Refresh(key);
|
|
|
|
public Task RefreshAsync(string key, CancellationToken token = default)
|
|
{
|
|
if (DisableRefreshAsync)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
if (DelayRefreshAsync)
|
|
{
|
|
token.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
|
|
token.ThrowIfCancellationRequested();
|
|
}
|
|
return _cache.RefreshAsync(key);
|
|
}
|
|
|
|
public void Remove(string key) => _cache.Remove(key);
|
|
|
|
public Task RemoveAsync(string key, CancellationToken token = default) => _cache.RemoveAsync(key);
|
|
|
|
public void Set(string key, byte[] value, DistributedCacheEntryOptions options) => _cache.Set(key, value, options);
|
|
|
|
public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default)
|
|
{
|
|
if (DisableSetAsync)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
if (DelaySetAsync)
|
|
{
|
|
token.WaitHandle.WaitOne(TimeSpan.FromSeconds(10));
|
|
token.ThrowIfCancellationRequested();
|
|
}
|
|
return _cache.SetAsync(key, value, options);
|
|
}
|
|
}
|
|
}
|
|
}
|