279 lines
10 KiB
C#
279 lines
10 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.Net;
|
|
using System.Net.Http;
|
|
using Microsoft.AspNetCore.WebUtilities;
|
|
using Microsoft.Extensions.Primitives;
|
|
using Microsoft.Net.Http.Headers;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Testing
|
|
{
|
|
public static class ResponseAssert
|
|
{
|
|
public static Uri IsRedirect(HttpResponseMessage responseMessage)
|
|
{
|
|
Assert.Equal(HttpStatusCode.Redirect, responseMessage.StatusCode);
|
|
return responseMessage.Headers.Location;
|
|
}
|
|
|
|
public static void IsOK(HttpResponseMessage responseMessage)
|
|
{
|
|
Assert.Equal(HttpStatusCode.OK, responseMessage.StatusCode);
|
|
}
|
|
|
|
public static SetCookieHeaderValue HasCookie(string name, HttpResponseMessage response, CookieComparison comparison)
|
|
{
|
|
var setCookieHeaderValue = new SetCookieHeaderValue(new StringSegment(name));
|
|
var foundCookie = HasCookieCore(setCookieHeaderValue, response, new SetCookieComparer(comparison));
|
|
|
|
if (foundCookie != null)
|
|
{
|
|
return foundCookie;
|
|
}
|
|
|
|
var suffix = comparison.HasFlag(CookieComparison.NameStartsWith) ? "starting with" : "";
|
|
Assert.True(false, $"Couldn't find a cookie with a name {suffix} '{name}'");
|
|
|
|
return null;
|
|
}
|
|
|
|
public static SetCookieHeaderValue HasCookie(
|
|
SetCookieHeaderValue expectedSetCookieHeader,
|
|
HttpResponseMessage response)
|
|
{
|
|
return HasCookie(expectedSetCookieHeader, response, SetCookieComparer.Default);
|
|
}
|
|
|
|
public static SetCookieHeaderValue HasCookie(
|
|
SetCookieHeaderValue expectedSetCookieHeader,
|
|
HttpResponseMessage response,
|
|
params CookieComparison[] criteria)
|
|
{
|
|
return HasCookie(expectedSetCookieHeader, response, new SetCookieComparer(criteria.Aggregate((l, r) => l | r)));
|
|
}
|
|
|
|
public static TCaptured LocationHasQueryParameters<TCaptured>(HttpResponseMessage response, params ValueSpecification[] values)
|
|
where TCaptured : new()
|
|
{
|
|
var queryParameters = LocationHasQueryParameters(response, values);
|
|
|
|
var result = new TCaptured();
|
|
|
|
return SimpleBind(result, queryParameters.ToDictionary(kvp => kvp.Key, kvp => (string[])kvp.Value, StringComparer.OrdinalIgnoreCase));
|
|
}
|
|
|
|
private static TCaptured SimpleBind<TCaptured>(TCaptured result, IDictionary<string, string[]> values)
|
|
{
|
|
foreach (var property in result.GetType().GetProperties())
|
|
{
|
|
if (values.TryGetValue(property.Name, out var value))
|
|
{
|
|
if (property.PropertyType == typeof(string))
|
|
{
|
|
property.SetValue(result, value.FirstOrDefault());
|
|
}
|
|
else if (property.PropertyType == typeof(string[]))
|
|
{
|
|
property.SetValue(result, value);
|
|
}
|
|
else
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public static IDictionary<string, StringValues> LocationHasQueryParameters(HttpResponseMessage response, params ValueSpecification[] values)
|
|
{
|
|
var location = IsRedirect(response);
|
|
|
|
var queryParameters = QueryHelpers.ParseNullableQuery(location.Query);
|
|
foreach (var parameter in values)
|
|
{
|
|
if (!queryParameters.TryGetValue(parameter.Name, out var parameterValues))
|
|
{
|
|
Assert.True(false, $"Missing parameter '{parameter}'");
|
|
}
|
|
|
|
if (!parameter.MatchValue)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
foreach (var expectedValue in parameter.Values)
|
|
{
|
|
Assert.Contains(expectedValue, parameterValues, parameter.ValueComparer);
|
|
}
|
|
}
|
|
|
|
return queryParameters;
|
|
}
|
|
|
|
public class ValueSpecification
|
|
{
|
|
public string Name { get; set; }
|
|
public bool MatchValue { get; set; } = true;
|
|
public string[] Values { get; set; }
|
|
public IEqualityComparer<string> ValueComparer { get; set; }
|
|
|
|
public static implicit operator ValueSpecification(string name)
|
|
{
|
|
return new ValueSpecification
|
|
{
|
|
Name = name,
|
|
MatchValue = false
|
|
};
|
|
}
|
|
|
|
public static implicit operator ValueSpecification((string name, string value) tuple) => new ValueSpecification()
|
|
{
|
|
Name = tuple.name,
|
|
Values = new string[] { tuple.value },
|
|
ValueComparer = StringComparer.Ordinal
|
|
};
|
|
|
|
public static implicit operator ValueSpecification((string name, string[] values) tuple) => new ValueSpecification()
|
|
{
|
|
Name = tuple.name,
|
|
Values = tuple.values,
|
|
ValueComparer = StringComparer.Ordinal
|
|
};
|
|
}
|
|
|
|
public static void IsHtmlDocument(HttpResponseMessage response)
|
|
{
|
|
Assert.True(response.IsSuccessStatusCode);
|
|
Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType);
|
|
}
|
|
|
|
public static SetCookieHeaderValue HasCookie(
|
|
SetCookieHeaderValue expectedCookieHeader,
|
|
HttpResponseMessage response,
|
|
IEqualityComparer<SetCookieHeaderValue> equalityComparer)
|
|
{
|
|
var foundValue = HasCookieCore(expectedCookieHeader, response, equalityComparer);
|
|
Assert.True(null != foundValue, $"Couldn't find a matching cookie.");
|
|
return foundValue;
|
|
}
|
|
|
|
private static SetCookieHeaderValue HasCookieCore(
|
|
SetCookieHeaderValue expectedCookieHeader,
|
|
HttpResponseMessage response,
|
|
IEqualityComparer<SetCookieHeaderValue> equalityComparer)
|
|
{
|
|
var values = SetCookieHeaderValue.ParseList(response.Headers.GetValues(HeaderNames.SetCookie).ToList());
|
|
foreach (var setCookieValue in values)
|
|
{
|
|
if (equalityComparer.Equals(expectedCookieHeader, setCookieValue))
|
|
{
|
|
return setCookieValue;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public class SetCookieComparer : IEqualityComparer<SetCookieHeaderValue>
|
|
{
|
|
public static SetCookieComparer Default = new SetCookieComparer(CookieComparison.Default);
|
|
private readonly CookieComparison _comparisonCriteria;
|
|
private readonly TimeSpan _skewAllowance;
|
|
|
|
public SetCookieComparer(CookieComparison comparisonCriteria, TimeSpan skewAllowance)
|
|
{
|
|
_comparisonCriteria = comparisonCriteria;
|
|
_skewAllowance = skewAllowance;
|
|
}
|
|
|
|
public SetCookieComparer(CookieComparison comparisonCriteria)
|
|
: this(comparisonCriteria, TimeSpan.FromMinutes(1))
|
|
{
|
|
}
|
|
|
|
public bool Equals(SetCookieHeaderValue expected, SetCookieHeaderValue candidate)
|
|
{
|
|
var matchesAllCriteria = true;
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.NameStartsWith))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && candidate.Name.StartsWith(expected.Name.ToString(), StringComparison.Ordinal);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.NameEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && candidate.Name.Equals(expected.Name, StringComparison.Ordinal);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.PathEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && candidate.Path.Equals(expected.Path, StringComparison.Ordinal);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.DomainEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && candidate.Domain.Equals(expected.Domain, StringComparison.Ordinal);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.ExpiresEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria &&
|
|
((expected.Expires.HasValue &&
|
|
candidate.Expires.HasValue &&
|
|
expected.Expires - _skewAllowance <= candidate.Expires &&
|
|
candidate.Expires <= expected.Expires + _skewAllowance) ||
|
|
expected.Expires == candidate.Expires);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.MaxAgeEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria &&
|
|
((expected.MaxAge.HasValue &&
|
|
candidate.MaxAge.HasValue &&
|
|
expected.MaxAge - _skewAllowance <= candidate.MaxAge &&
|
|
candidate.MaxAge <= expected.MaxAge + _skewAllowance) ||
|
|
expected.MaxAge == candidate.MaxAge);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.HttpOnly))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && expected.HttpOnly == candidate.HttpOnly;
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.SameSite))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && expected.SameSite == candidate.SameSite;
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.Secure))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && expected.Secure == candidate.Secure;
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.ValueEquals))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && expected.Value.Equals(candidate.Value, StringComparison.Ordinal);
|
|
}
|
|
|
|
if (_comparisonCriteria.HasFlag(CookieComparison.ValueStartsWith))
|
|
{
|
|
matchesAllCriteria = matchesAllCriteria && candidate.Value.StartsWith(expected.Value.ToString(), StringComparison.Ordinal);
|
|
}
|
|
|
|
return matchesAllCriteria;
|
|
}
|
|
|
|
public int GetHashCode(SetCookieHeaderValue obj)
|
|
{
|
|
return 1;
|
|
}
|
|
}
|
|
}
|