diff --git a/README.md b/README.md index f541c00..a6872c4 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,8 @@ await Expect.That(request).HasHeader("Cache-Control").WithValue("max-age=0"); ```text title="Failure message" Expected that request -has a `Cache-Control` header whose value is equal to "max-age=0", -but the value was "no-cache", which differs at index 0: +has a "Cache-Control" header whose value is equal to "max-age=0", +but it had header value "no-cache", which differs at index 0: ↓ (actual) "no-cache" "max-age=0" @@ -233,7 +233,7 @@ await Expect.That(response).HasContentType("text/plain"); ```text title="Failure message" Expected that response has a `Content-Type` header equal to "text/plain", -but it was "application/json", which differs at index 0: +but it had content type "application/json", which differs at index 0: ↓ (actual) "application/json" "text/plain" diff --git a/Source/aweXpect.Web/Helpers/ThrowHelper.cs b/Source/aweXpect.Web/Helpers/ThrowHelper.cs index 9a669db..bbd0fe4 100644 --- a/Source/aweXpect.Web/Helpers/ThrowHelper.cs +++ b/Source/aweXpect.Web/Helpers/ThrowHelper.cs @@ -12,4 +12,17 @@ public static void ThrowIfNull(object? value, string paramName) throw Tracing.WriteException(new ArgumentNullException(paramName, $"The '{paramName}' cannot be null.")); } } + + /// + /// Rejects the when it , because the later + /// value would silently replace the earlier one. + /// + public static void ThrowIfOptionIsAlreadySpecified(bool isAlreadySpecified, string option) + { + if (isAlreadySpecified) + { + throw Tracing.WriteException(new InvalidOperationException( + $"{option} cannot be specified more than once.")); + } + } } diff --git a/Source/aweXpect.Web/ThatHttpRequestMessage.HasContent.cs b/Source/aweXpect.Web/ThatHttpRequestMessage.HasContent.cs index 8d67fe0..bc01789 100644 --- a/Source/aweXpect.Web/ThatHttpRequestMessage.HasContent.cs +++ b/Source/aweXpect.Web/ThatHttpRequestMessage.HasContent.cs @@ -93,7 +93,7 @@ public async ValueTask IsMetBy( } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has a string content ") + => stringBuilder.Append(Grammars.Verb("has a string content ", "have a string content ")) .Append(options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) @@ -104,12 +104,12 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else { - stringBuilder.Append(options.GetExtendedFailure(It, Grammars, _message, expected)); + stringBuilder.Append(options.GetExtendedMemberFailure(It, "string content", Grammars, _message, expected)); } } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have a string content ") + => stringBuilder.Append(Grammars.Verb("does not have a string content ", "do not have a string content ")) .Append(options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Source/aweXpect.Web/ThatHttpRequestMessage.HasHeader.cs b/Source/aweXpect.Web/ThatHttpRequestMessage.HasHeader.cs index cd35a0a..4c7f4f9 100644 --- a/Source/aweXpect.Web/ThatHttpRequestMessage.HasHeader.cs +++ b/Source/aweXpect.Web/ThatHttpRequestMessage.HasHeader.cs @@ -76,19 +76,28 @@ public ConstraintResult IsMetBy(HttpRequestMessage? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has ").Append(expected.IndefiniteArticleForHeader()).Append(" `").Append(expected) - .Append("` header"); + { + stringBuilder.Append(Grammars.Verb("has ", "have ")).Append(expected.IndefiniteArticleForHeader()).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header"); + } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) => stringBuilder.Append(It).Append(" did not contain the expected header"); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have ").Append(expected.IndefiniteArticleForHeader()).Append(" `") - .Append(expected).Append("` header"); + { + stringBuilder.Append(Grammars.Verb("does not have ", "do not have ")) + .Append(expected.IndefiniteArticleForHeader()).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header"); + } protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) { - stringBuilder.Append(It).Append(" did contain the `").Append(expected).Append("` header: "); + stringBuilder.Append(It).Append(" did contain the "); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header: "); Formatter.Format(stringBuilder, _foundHeader); } } diff --git a/Source/aweXpect.Web/ThatHttpRequestMessage.HasMethod.cs b/Source/aweXpect.Web/ThatHttpRequestMessage.HasMethod.cs index 28d6b34..b0cdb4e 100644 --- a/Source/aweXpect.Web/ThatHttpRequestMessage.HasMethod.cs +++ b/Source/aweXpect.Web/ThatHttpRequestMessage.HasMethod.cs @@ -57,7 +57,11 @@ public override string ToString() => $"has {Article} {expected} method"; protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has ").Append(Article).Append(' ').Append(expected).Append(" method"); + { + stringBuilder.Append(Grammars.Verb("has ", "have ")).Append(Article).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" method"); + } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -66,7 +70,11 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have ").Append(Article).Append(' ').Append(expected).Append(" method"); + { + stringBuilder.Append(Grammars.Verb("does not have ", "do not have ")).Append(Article).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" method"); + } protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => stringBuilder.Append(It).Append(" had"); diff --git a/Source/aweXpect.Web/ThatHttpRequestMessage.HasRequestUri.cs b/Source/aweXpect.Web/ThatHttpRequestMessage.HasRequestUri.cs index 6e51d4b..636caf2 100644 --- a/Source/aweXpect.Web/ThatHttpRequestMessage.HasRequestUri.cs +++ b/Source/aweXpect.Web/ThatHttpRequestMessage.HasRequestUri.cs @@ -58,6 +58,7 @@ private sealed class HasRequestUriConstraint( : ConstraintResult.WithNotNullValue(it, grammars), IValueConstraint { + private readonly StringEqualityOptions _options = new StringEqualityOptions(nameof(expected)).IgnoringCase(); private string? _requestUri; public ConstraintResult IsMetBy(HttpRequestMessage? actual) @@ -81,20 +82,15 @@ public ConstraintResult IsMetBy(HttpRequestMessage? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - { - stringBuilder.Append("has a request URI equal to "); - Formatter.Format(stringBuilder, expected); - } + => stringBuilder.Append(Grammars.Verb("has a request URI ", "have a request URI ")) + .Append(_options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(new StringEqualityOptions(nameof(expected)).IgnoringCase() - .GetExtendedFailure(It, Grammars, _requestUri, expected)); + => stringBuilder.Append(_options.GetExtendedMemberFailure(It, "request URI", Grammars, _requestUri, expected)); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - { - stringBuilder.Append("does not have a request URI equal to "); - Formatter.Format(stringBuilder, expected); - } + => stringBuilder.Append(Grammars.Verb("does not have a request URI ", "do not have a request URI ")) + .Append(_options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => stringBuilder.Append(It).Append(" had"); diff --git a/Source/aweXpect.Web/ThatHttpResponseMessage.HasContent.cs b/Source/aweXpect.Web/ThatHttpResponseMessage.HasContent.cs index 5fc15a3..0962b77 100644 --- a/Source/aweXpect.Web/ThatHttpResponseMessage.HasContent.cs +++ b/Source/aweXpect.Web/ThatHttpResponseMessage.HasContent.cs @@ -87,7 +87,7 @@ public async ValueTask IsMetBy( } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has a string content ") + => stringBuilder.Append(Grammars.Verb("has a string content ", "have a string content ")) .Append(options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) @@ -98,12 +98,12 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else { - stringBuilder.Append(options.GetExtendedFailure(It, Grammars, _message, expected)); + stringBuilder.Append(options.GetExtendedMemberFailure(It, "string content", Grammars, _message, expected)); } } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have a string content ") + => stringBuilder.Append(Grammars.Verb("does not have a string content ", "do not have a string content ")) .Append(options.GetExpectation(expected, ExpectationGrammars.None)); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Source/aweXpect.Web/ThatHttpResponseMessage.HasContentType.cs b/Source/aweXpect.Web/ThatHttpResponseMessage.HasContentType.cs index e9b56b9..858f91e 100644 --- a/Source/aweXpect.Web/ThatHttpResponseMessage.HasContentType.cs +++ b/Source/aweXpect.Web/ThatHttpResponseMessage.HasContentType.cs @@ -72,7 +72,7 @@ public async ValueTask IsMetBy(HttpResponseMessage? actual, Ca } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has a `Content-Type` header ") + => stringBuilder.Append(Grammars.Verb("has a `Content-Type` header ", "have a `Content-Type` header ")) .Append(options.GetExpectation(expected, Grammars)); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) @@ -83,12 +83,12 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else { - stringBuilder.Append(options.GetExtendedFailure(It, Grammars, _contentType, expected)); + stringBuilder.Append(options.GetExtendedMemberFailure(It, "content type", Grammars, _contentType, expected)); } } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have a `Content-Type` header ") + => stringBuilder.Append(Grammars.Verb("does not have a `Content-Type` header ", "do not have a `Content-Type` header ")) .Append(options.GetExpectation(expected, Grammars.Negate())); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) diff --git a/Source/aweXpect.Web/ThatHttpResponseMessage.HasHeader.cs b/Source/aweXpect.Web/ThatHttpResponseMessage.HasHeader.cs index 847d517..2ae5b0f 100644 --- a/Source/aweXpect.Web/ThatHttpResponseMessage.HasHeader.cs +++ b/Source/aweXpect.Web/ThatHttpResponseMessage.HasHeader.cs @@ -76,19 +76,28 @@ public ConstraintResult IsMetBy(HttpResponseMessage? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has ").Append(expected.IndefiniteArticleForHeader()).Append(" `").Append(expected) - .Append("` header"); + { + stringBuilder.Append(Grammars.Verb("has ", "have ")).Append(expected.IndefiniteArticleForHeader()).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header"); + } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) => stringBuilder.Append(It).Append(" did not contain the expected header"); protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have ").Append(expected.IndefiniteArticleForHeader()).Append(" `") - .Append(expected).Append("` header"); + { + stringBuilder.Append(Grammars.Verb("does not have ", "do not have ")) + .Append(expected.IndefiniteArticleForHeader()).Append(' '); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header"); + } protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) { - stringBuilder.Append(It).Append(" did contain the `").Append(expected).Append("` header: "); + stringBuilder.Append(It).Append(" did contain the "); + Formatter.Format(stringBuilder, expected); + stringBuilder.Append(" header: "); Formatter.Format(stringBuilder, _foundHeader); } } diff --git a/Source/aweXpect.Web/ThatHttpResponseMessage.HasProblemDetailsContent.cs b/Source/aweXpect.Web/ThatHttpResponseMessage.HasProblemDetailsContent.cs index 4534d9b..98a823a 100644 --- a/Source/aweXpect.Web/ThatHttpResponseMessage.HasProblemDetailsContent.cs +++ b/Source/aweXpect.Web/ThatHttpResponseMessage.HasProblemDetailsContent.cs @@ -210,13 +210,14 @@ private static string DescribeKind(JsonValueKind kind) protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) { + stringBuilder.Append(Grammars.Verb("has a ProblemDetails content with ", "have a ProblemDetails content with ")); if (expectedType is null) { - stringBuilder.Append("has a ProblemDetails content with any type"); + stringBuilder.Append("any type"); } else { - stringBuilder.Append("has a ProblemDetails content with type "); + stringBuilder.Append("type "); Formatter.Format(stringBuilder, expectedType); stringBuilder.Append(typeOptions); } @@ -240,13 +241,10 @@ private void AppendParseError(StringBuilder stringBuilder) protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) { - if (expectedType is null) - { - stringBuilder.Append("does not have a ProblemDetails content"); - } - else + stringBuilder.Append(Grammars.Verb("does not have a ProblemDetails content", "do not have a ProblemDetails content")); + if (expectedType is not null) { - stringBuilder.Append("does not have a ProblemDetails content with type "); + stringBuilder.Append(" with type "); Formatter.Format(stringBuilder, expectedType); stringBuilder.Append(typeOptions); } diff --git a/Source/aweXpect.Web/ThatHttpResponseMessage.HasStatusCode.cs b/Source/aweXpect.Web/ThatHttpResponseMessage.HasStatusCode.cs index 6b75f57..9994cd8 100644 --- a/Source/aweXpect.Web/ThatHttpResponseMessage.HasStatusCode.cs +++ b/Source/aweXpect.Web/ThatHttpResponseMessage.HasStatusCode.cs @@ -31,6 +31,5 @@ public static StatusCodeResult HasStatusCode(this IThat so expected, m => m.StatusCode, (a, e) => a.Equals(e), - $"has status code {Formatter.Format(expected)}", - $"does not have status code {Formatter.Format(expected)}")), source); + $"status code {Formatter.Format(expected)}")), source); } diff --git a/Source/aweXpect.Web/ThatUri.HasDefaultPort.cs b/Source/aweXpect.Web/ThatUri.HasDefaultPort.cs index f281216..771e193 100644 --- a/Source/aweXpect.Web/ThatUri.HasDefaultPort.cs +++ b/Source/aweXpect.Web/ThatUri.HasDefaultPort.cs @@ -46,7 +46,7 @@ public ConstraintResult IsMetBy(Uri? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("has the default port for the used scheme"); + => stringBuilder.Append(Grammars.Verb("has ", "have ")).Append("the default port for the used scheme"); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -55,7 +55,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not have the default port for the used scheme"); + => stringBuilder.Append(Grammars.Verb("does not have ", "do not have ")).Append("the default port for the used scheme"); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect.Web/ThatUri.IsAbsolute.cs b/Source/aweXpect.Web/ThatUri.IsAbsolute.cs index 84cf284..0b96a16 100644 --- a/Source/aweXpect.Web/ThatUri.IsAbsolute.cs +++ b/Source/aweXpect.Web/ThatUri.IsAbsolute.cs @@ -46,7 +46,7 @@ public ConstraintResult IsMetBy(Uri? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is an absolute URI"); + => stringBuilder.Append(Grammars.Verb("is an absolute URI", "are absolute URIs")); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -55,7 +55,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is not an absolute URI"); + => stringBuilder.Append(Grammars.Verb("is not an absolute URI", "are not absolute URIs")); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect.Web/ThatUri.IsFile.cs b/Source/aweXpect.Web/ThatUri.IsFile.cs index ea249fc..b45a4ee 100644 --- a/Source/aweXpect.Web/ThatUri.IsFile.cs +++ b/Source/aweXpect.Web/ThatUri.IsFile.cs @@ -46,7 +46,7 @@ public ConstraintResult IsMetBy(Uri? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is a file URI"); + => stringBuilder.Append(Grammars.Verb("is a file URI", "are file URIs")); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -55,7 +55,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is not a file URI"); + => stringBuilder.Append(Grammars.Verb("is not a file URI", "are not file URIs")); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect.Web/ThatUri.IsLoopback.cs b/Source/aweXpect.Web/ThatUri.IsLoopback.cs index a8828fd..9052c74 100644 --- a/Source/aweXpect.Web/ThatUri.IsLoopback.cs +++ b/Source/aweXpect.Web/ThatUri.IsLoopback.cs @@ -46,7 +46,7 @@ public ConstraintResult IsMetBy(Uri? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("references the local host"); + => stringBuilder.Append(Grammars.Verb("references ", "reference ")).Append("the local host"); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -55,7 +55,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("does not reference the local host"); + => stringBuilder.Append(Grammars.Verb("does not ", "do not ")).Append("reference the local host"); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect.Web/ThatUri.IsUnc.cs b/Source/aweXpect.Web/ThatUri.IsUnc.cs index 389332c..241b7b3 100644 --- a/Source/aweXpect.Web/ThatUri.IsUnc.cs +++ b/Source/aweXpect.Web/ThatUri.IsUnc.cs @@ -46,7 +46,7 @@ public ConstraintResult IsMetBy(Uri? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is an UNC path"); + => stringBuilder.Append(Grammars.Verb("is an UNC path", "are UNC paths")); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -55,7 +55,7 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append("is not an UNC path"); + => stringBuilder.Append(Grammars.Verb("is not an UNC path", "are not UNC paths")); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Source/aweXpect.Web/Web/Results/HasHeaderValueResult.cs b/Source/aweXpect.Web/Web/Results/HasHeaderValueResult.cs index 39f6e5d..ced2efa 100644 --- a/Source/aweXpect.Web/Web/Results/HasHeaderValueResult.cs +++ b/Source/aweXpect.Web/Web/Results/HasHeaderValueResult.cs @@ -41,8 +41,8 @@ internal HasHeaderValueResult( public StringEqualityResult WithValue(string? expected) { StringEqualityOptions options = new(nameof(expected)); - _expectationBuilder.And("").AddConstraint((_, grammars) => - new WithHeaderValueConstraint("the value", grammars, expected, _headerValueAccessor, options)); + _expectationBuilder.And("").AddConstraint((it, grammars) => + new WithHeaderValueConstraint(it, grammars, expected, _headerValueAccessor, options)); return new StringEqualityResult(_expectationBuilder, _returnValue, options); } @@ -118,10 +118,14 @@ public async ValueTask IsMetBy(TType? actual, CancellationToke return this; } + /// + /// The text continues the one of the header, which carries the negation and the number of the subject, like + /// the member expectations of Whose do. + /// protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) { stringBuilder.Append(" whose value "); - stringBuilder.Append(options.GetExpectation(expected, Grammars | ExpectationGrammars.Active)); + stringBuilder.Append(options.GetExpectation(expected, ExpectationGrammars.Active)); } protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) @@ -132,22 +136,23 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } else if (_headerValues.Length != 1) { - stringBuilder.Append("the header contained ").Append(_headerValues.Length).Append(" values "); + stringBuilder.Append(It).Append(" had ").Append(_headerValues.Length).Append(" header values "); Formatter.Format(stringBuilder, _headerValues); } else { - stringBuilder.Append(options.GetExtendedFailure(It, Grammars, _headerValues[0], expected)); + AppendValueFailure(stringBuilder); } } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - { - stringBuilder.Append(" whose value "); - stringBuilder.Append(options.GetExpectation(expected, Grammars | ExpectationGrammars.Active)); - } + => AppendNormalExpectation(stringBuilder, indentation); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(It).Append(" was"); + => AppendValueFailure(stringBuilder); + + private void AppendValueFailure(StringBuilder stringBuilder) + => stringBuilder.Append(options.GetExtendedMemberFailure(It, "header value", Grammars, _headerValues?[0], + expected)); } } diff --git a/Source/aweXpect.Web/Web/Results/ProblemDetailsOptions.cs b/Source/aweXpect.Web/Web/Results/ProblemDetailsOptions.cs index 6a7ed6f..1867284 100644 --- a/Source/aweXpect.Web/Web/Results/ProblemDetailsOptions.cs +++ b/Source/aweXpect.Web/Web/Results/ProblemDetailsOptions.cs @@ -64,6 +64,7 @@ public class ProblemDetailsOptions public StringEqualityOptions WithTitle(string title) { ThrowHelper.ThrowIfNull(title, nameof(title)); + ThrowHelper.ThrowIfOptionIsAlreadySpecified(Title is not null, nameof(WithTitle)); Title = title; _parts.Add(() => $"title {Formatter.Format(title)}{_titleOptions}"); return _titleOptions; @@ -74,6 +75,7 @@ public StringEqualityOptions WithTitle(string title) /// public void WithStatus(int status) { + ThrowHelper.ThrowIfOptionIsAlreadySpecified(Status is not null, nameof(WithStatus)); Status = status; _parts.Add(() => $"status {Formatter.Format(status)}"); } @@ -84,6 +86,7 @@ public void WithStatus(int status) public StringEqualityOptions WithDetail(string detail) { ThrowHelper.ThrowIfNull(detail, nameof(detail)); + ThrowHelper.ThrowIfOptionIsAlreadySpecified(Detail is not null, nameof(WithDetail)); Detail = detail; _parts.Add(() => $"detail {Formatter.Format(detail)}{_detailOptions}"); return _detailOptions; @@ -95,6 +98,7 @@ public StringEqualityOptions WithDetail(string detail) public StringEqualityOptions WithInstance(string instance) { ThrowHelper.ThrowIfNull(instance, nameof(instance)); + ThrowHelper.ThrowIfOptionIsAlreadySpecified(Instance is not null, nameof(WithInstance)); Instance = instance; _parts.Add(() => $"instance {Formatter.Format(instance)}{_instanceOptions}"); return _instanceOptions; diff --git a/Source/aweXpect.Web/Web/Results/StatusCodeResult.cs b/Source/aweXpect.Web/Web/Results/StatusCodeResult.cs index 29dde22..a55467e 100644 --- a/Source/aweXpect.Web/Web/Results/StatusCodeResult.cs +++ b/Source/aweXpect.Web/Web/Results/StatusCodeResult.cs @@ -29,8 +29,7 @@ public class StatusCodeResult( expected, mapper, (a, e) => a.Equals(e), - $"has status code {Formatter.Format(expected)}", - $"does not have status code {Formatter.Format(expected)}")), + $"status code {Formatter.Format(expected)}")), source); /// @@ -46,8 +45,8 @@ public class StatusCodeResult( unexpected, mapper, (a, u) => !a.Equals(u), - $"has status code different to {Formatter.Format(unexpected)}", - $"has status code {Formatter.Format(unexpected)}")), + $"status code different to {Formatter.Format(unexpected)}", + $"status code {Formatter.Format(unexpected)}")), source); /// @@ -62,8 +61,7 @@ public class StatusCodeResult( null, mapper, (a, _) => (int)a is >= 200 and < 300, - "has a success status code (2xx)", - "does not have a success status code (2xx)")), + "a success status code (2xx)")), source); /// @@ -78,8 +76,7 @@ public class StatusCodeResult( null, mapper, (a, _) => (int)a is >= 300 and < 400, - "has a redirection status code (3xx)", - "does not have a redirection status code (3xx)")), + "a redirection status code (3xx)")), source); /// @@ -94,8 +91,7 @@ public class StatusCodeResult( null, mapper, (a, _) => (int)a is >= 400 and < 500, - "has a client error status code (4xx)", - "does not have a client error status code (4xx)")), + "a client error status code (4xx)")), source); /// @@ -110,8 +106,7 @@ public class StatusCodeResult( null, mapper, (a, _) => (int)a is >= 500 and < 600, - "has a server error status code (5xx)", - "does not have a server error status code (5xx)")), + "a server error status code (5xx)")), source); /// @@ -126,8 +121,7 @@ public class StatusCodeResult( null, mapper, (a, _) => (int)a is >= 400 and < 600, - "has an error status code (4xx or 5xx)", - "does not have an error status code (4xx or 5xx)")), + "an error status code (4xx or 5xx)")), source); internal sealed class PropertyConstraint( @@ -137,7 +131,7 @@ internal sealed class PropertyConstraint( Func mapper, Func condition, string expectation, - string negatedExpectation) + string? hasExpectationWhenNegated = null) : ConstraintResult.WithNotNullValue(it, grammars), IValueConstraint { @@ -164,7 +158,7 @@ public ConstraintResult IsMetBy(HttpResponseMessage? actual) } protected override void AppendNormalExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(expectation); + => stringBuilder.Append(Grammars.Verb("has ", "have ")).Append(expectation); protected override void AppendNormalResult(StringBuilder stringBuilder, string? indentation = null) { @@ -173,7 +167,9 @@ protected override void AppendNormalResult(StringBuilder stringBuilder, string? } protected override void AppendNegatedExpectation(StringBuilder stringBuilder, string? indentation = null) - => stringBuilder.Append(negatedExpectation); + => stringBuilder.Append(hasExpectationWhenNegated is null + ? Grammars.Verb("does not have ", "do not have ") + expectation + : Grammars.Verb("has ", "have ") + hasExpectationWhenNegated); protected override void AppendNegatedResult(StringBuilder stringBuilder, string? indentation = null) => AppendNormalResult(stringBuilder, indentation); diff --git a/Tests/Aot/aweXpect.Web.Aot/Checks.cs b/Tests/Aot/aweXpect.Web.Aot/Checks.cs index bd59ce6..36b96d2 100644 --- a/Tests/Aot/aweXpect.Web.Aot/Checks.cs +++ b/Tests/Aot/aweXpect.Web.Aot/Checks.cs @@ -55,10 +55,10 @@ internal static class Checks .HasHeader("x-track").WithValue("42"))), new("a missing header fails and is named", () => ShouldFail(async () => await That(Response(HttpStatusCode.OK)).HasHeader("x-track"), - "has an `x-track` header")), + "has an \"x-track\" header")), new("an unexpected header fails and is named", () => ShouldFail(async () => await That(Response(HttpStatusCode.OK, header: "x-track")).DoesNotHaveHeader("x-track"), - "does not have an `x-track` header","[\"42\"]")), + "does not have an \"x-track\" header","[\"42\"]")), new("an equal content passes", () => ShouldPass(async () => await That(Response(HttpStatusCode.OK, Json)).HasContent(Json))), new("a differing content fails and shows both", diff --git a/Tests/aweXpect.Web.Samples.Tests/CoverTests.cs b/Tests/aweXpect.Web.Samples.Tests/CoverTests.cs index fef7ef1..2871abd 100644 --- a/Tests/aweXpect.Web.Samples.Tests/CoverTests.cs +++ b/Tests/aweXpect.Web.Samples.Tests/CoverTests.cs @@ -33,7 +33,7 @@ await Expect.That(Act).Throws() .WithMessage(""" Expected that response has a `Content-Type` header equal to "image/jpg", - but it was "image/png", which differs at index 6: + but it had content type "image/png", which differs at index 6: ↓ (actual) "image/png" "image/jpg" diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.CombinedTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.CombinedTests.cs index 3fc2df2..b69f747 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.CombinedTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.CombinedTests.cs @@ -19,13 +19,13 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a string content equal to "bar" and has a request URI equal to "https://example.com/", - but it was "foo", which differs at index 0: + has a string content equal to "bar" and has a request URI equal to "https://example.com/" ignoring case, + but it had string content "foo", which differs at index 0: ↓ (actual) "foo" "bar" ↑ (expected) - and it was "https://awexpect.com/", which differs at index 8: + and it had request URI "https://awexpect.com/", which differs at index 8: ↓ (actual) "https://awexpect.com/" "https://example.com/" @@ -53,7 +53,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a POST method and has a `y` header, + has a POST method and has a "y" header, but it was GET and did not contain the expected header HTTP-Request: diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.DoesNotHaveHeader.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.DoesNotHaveHeader.Tests.cs index af5e322..734dc9a 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.DoesNotHaveHeader.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.DoesNotHaveHeader.Tests.cs @@ -8,6 +8,21 @@ public sealed class DoesNotHaveHeader { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder.WithHeader("x-my-header", "foo"),]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.DoesNotHaveHeader("x-my-header"))); + + await That(Act).Throws() + .WithMessage("""*whose Requests do not have an "x-my-header" header for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenUnexpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ async Task Act() await That(Act).Throws() .WithMessage($""" Expected that subject - does not have {article} `{name}` header, + does not have {article} "{name}" header, but it was """); } @@ -69,8 +84,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-my-header` header, - but it did contain the `x-my-header` header: ["some header"] + does not have an "x-my-header" header, + but it did contain the "x-my-header" header: ["some header"] HTTP-Request: HEAD https://awexpect.com/ HTTP/1.1 @@ -92,7 +107,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-my-header` header, + does not have an "x-my-header" header, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasContent.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasContent.Tests.cs index 128376d..6df9e3f 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasContent.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasContent.Tests.cs @@ -8,6 +8,21 @@ public sealed partial class HasContent { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.HasContent("bar"))); + + await That(Act).Throws() + .WithMessage("""*whose Requests have a string content equal to "bar" for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a string content equal to "other content", - but it was "some content", which differs at index 0: + but it had string content "some content", which differs at index 0: ↓ (actual) "some content" "other content" @@ -66,7 +81,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a string content equal to "other content", - but it was "some content", which differs at index 0: + but it had string content "some content", which differs at index 0: ↓ (actual) "some content" "other content" diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.Tests.cs index c269052..d2e5695 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.Tests.cs @@ -8,6 +8,21 @@ public sealed partial class HasHeader { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder,]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.HasHeader("x-my-header"))); + + await That(Act).Throws() + .WithMessage("""*whose Requests have an "x-my-header" header for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ async Task Act() await That(Act).Throws() .WithMessage($""" Expected that subject - has {article} `{name}` header, + has {article} "{name}" header, but it was """); } @@ -55,7 +70,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header, + has an "x-my-header" header, but it did not contain the expected header HTTP-Request: @@ -90,7 +105,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header, + has an "x-my-header" header, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValueTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValueTests.cs index 48fb1c1..5c4ae42 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValueTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValueTests.cs @@ -36,7 +36,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose value is equal to "some header", + has an "x-some-other-key" header whose value is equal to "some header", but it did not contain the expected header HTTP-Request: @@ -60,7 +60,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose value is equal to "some other header", + has an "x-my-header" header whose value is equal to "some other header", but the value was "some header", which differs at index 5: ↓ (actual) "some header" @@ -98,7 +98,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose value is empty, + has an "x-my-key" header whose value is empty, but it was """); } @@ -153,8 +153,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-my-header` header whose value is equal to "some header", - but it did contain the `x-my-header` header: ["some header"] + does not have an "x-my-header" header whose value is equal to "some header", + but it did contain the "x-my-header" header: ["some header"] HTTP-Request: HEAD https://awexpect.com/ HTTP/1.1 diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValuesTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValuesTests.cs index efa3f79..f93aebd 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValuesTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WhoseValuesTests.cs @@ -36,7 +36,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose values have exactly 3 items, + has an "x-some-other-key" header whose values have exactly 3 items, but it did not contain the expected header HTTP-Request: @@ -62,7 +62,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose values contain "some other header" at least once, + has an "x-my-header" header whose values contain "some other header" at least once, but the values did not contain it HTTP-Request: @@ -101,7 +101,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose values are empty, + has an "x-my-key" header whose values are empty, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WithValueTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WithValueTests.cs index 76e2e45..74fe117 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WithValueTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasHeader.WithValueTests.cs @@ -36,7 +36,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose value is equal to "some header", + has an "x-some-other-key" header whose value is equal to "some header", but it did not contain the expected header HTTP-Request: @@ -60,8 +60,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose value is equal to "some other header", - but the value was "some header", which differs at index 5: + has an "x-my-header" header whose value is equal to "some other header", + but it had header value "some header", which differs at index 5: ↓ (actual) "some header" "some other header" @@ -101,7 +101,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose value is equal to "some header", + has an "x-some-other-key" header whose value is equal to "some header", but it did not contain the expected header HTTP-Request: @@ -121,10 +121,48 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose value is equal to "foo", + has an "x-my-key" header whose value is equal to "foo", but it was """); } } + + public sealed class NegatedWithValueTests + { + [Fact] + public async Task WhenHeaderExistsAndValueDiffers_ShouldSucceed() + { + HttpRequestMessage subject = RequestBuilder + .WithHeader("x-my-header", "some header"); + + async Task Act() + => await That(subject) + .DoesNotComplyWith(it => it.HasHeader("x-my-header").WithValue("some other header")); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenHeaderExistsAndValueIsEqual_ShouldFail() + { + HttpRequestMessage subject = RequestBuilder + .WithHeader("x-my-header", "some header"); + + async Task Act() + => await That(subject).DoesNotComplyWith(it => it.HasHeader("x-my-header").WithValue("some header")); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + does not have an "x-my-header" header whose value is equal to "some header", + but it did contain the "x-my-header" header: ["some header"] and had header value "some header" + + HTTP-Request: + HEAD https://awexpect.com/ HTTP/1.1 + x-my-header: some header + """) + .Because("the negation applies to the header as a whole, like the negated text of a `Whose`"); + } + } } } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasMethod.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasMethod.Tests.cs index c69bcb5..9281a59 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasMethod.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasMethod.Tests.cs @@ -8,6 +8,36 @@ public sealed class HasMethod { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder,]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(request => request.HasMethod(HttpMethod.Head)))); + + await That(Act).Throws() + .WithMessage("""*whose Requests do not have a HEAD method for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder,]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.HasMethod(HttpMethod.Get))); + + await That(Act).Throws() + .WithMessage("""*whose Requests have a GET method for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasRequestUri.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasRequestUri.Tests.cs index 37ed09b..0df2009 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasRequestUri.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpRequestMessage.HasRequestUri.Tests.cs @@ -8,6 +8,36 @@ public sealed class HasRequestUri { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder,]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(request => request.HasRequestUri("https://awexpect.com/")))); + + await That(Act).Throws() + .WithMessage("""*whose Requests do not have a request URI equal to "https://awexpect.com/" ignoring case for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpRequestMessage[] subjects = [RequestBuilder,]; + + async Task Act() + => await That(new { Requests = subjects, }) + .Whose(x => x.Requests, items => items.All() + .ComplyWith(item => item.HasRequestUri("https://example.com/"))); + + await That(Act).Throws() + .WithMessage("""*whose Requests have a request URI equal to "https://example.com/" ignoring case for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedStringIsNull_ShouldThrowArgumentNullException() { @@ -61,7 +91,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a request URI equal to "https://awexpect.com/", + has a request URI equal to "https://awexpect.com/" ignoring case, but it was """); } @@ -79,8 +109,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a request URI equal to "https://awexpect.com/awexpect.Web", - but it was "https://awexpect.com/" with a length of 21, which is shorter than the expected length of 33 and misses: + has a request URI equal to "https://awexpect.com/awexpect.…" ignoring case, + but it had request URI "https://awexpect.com/" with a length of 21, which is shorter than the expected length of 33 and misses: "awexpect.Web" HTTP-Request: @@ -101,8 +131,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a request URI equal to "https://awexpect.com/abd", - but it was "https://awexpect.com/ABC", which differs at index 23: + has a request URI equal to "https://awexpect.com/abd" ignoring case, + but it had request URI "https://awexpect.com/ABC", which differs at index 23: ↓ (actual) "…ect.com/ABC" "…ect.com/abd" @@ -140,7 +170,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have a request URI equal to "https://awexpect.com/", + does not have a request URI equal to "https://awexpect.com/" ignoring case, but it was """); } @@ -171,7 +201,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have a request URI equal to "https://awexpect.com/", + does not have a request URI equal to "https://awexpect.com/" ignoring case, but it had HTTP-Request: diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.CombinedTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.CombinedTests.cs index b400ff1..3a64325 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.CombinedTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.CombinedTests.cs @@ -23,7 +23,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-b` header and has a success status code (2xx) for all items, + has an "x-b" header and has a success status code (2xx) for all items, but none of 2 did * HTTP-Response (item [0]): @@ -47,8 +47,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-a` header and has a success status code (2xx), - but it did contain the `x-a` header: ["1"] and had status code 404 NotFound + does not have an "x-a" header and has a success status code (2xx), + but it did contain the "x-a" header: ["1"] and had status code 404 NotFound HTTP-Response: 404 NotFound HTTP/1.1 @@ -79,7 +79,7 @@ Expected that subject "foo" "bar" ↑ (expected) - and it was "foo", which differs at index 0: + and it had string content "foo", which differs at index 0: ↓ (actual) "foo" "baz" @@ -107,7 +107,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-a` header whose value is equal to "2" and has status code 201 Created, + has an "x-a" header whose value is equal to "2" and has status code 201 Created, but the value was "1", which differs at index 0: ↓ (actual) "1" @@ -137,7 +137,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a request message which has a `y` header and has an `x` header, + has a request message which has a "y" header and has an "x" header, but it did not contain the expected header HTTP-Request: @@ -198,7 +198,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a success status code (2xx) and has a `Content-Type` header equal to "application/json", - but it had status code 404 NotFound and was "text/plain", which differs at index 0: + but it had status code 404 NotFound and had content type "text/plain", which differs at index 0: ↓ (actual) "text/plain" "application/json" @@ -225,7 +225,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a success status code (2xx) and has an `x-missing` header, + has a success status code (2xx) and has an "x-missing" header, but it had status code 404 NotFound and did not contain the expected header HTTP-Response: @@ -249,7 +249,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has a success status code (2xx) or has an `x-missing` header, + has a success status code (2xx) or has an "x-missing" header, but it had status code 404 NotFound and did not contain the expected header HTTP-Response: diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.DoesNotHaveHeader.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.DoesNotHaveHeader.Tests.cs index bdb52c0..607df8c 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.DoesNotHaveHeader.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.DoesNotHaveHeader.Tests.cs @@ -8,6 +8,21 @@ public sealed class DoesNotHaveHeader { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithHeader("x-my-header", "foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.DoesNotHaveHeader("x-my-header"))); + + await That(Act).Throws() + .WithMessage("""*whose Responses do not have an "x-my-header" header for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenUnexpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ async Task Act() await That(Act).Throws() .WithMessage($""" Expected that subject - does not have {article} `{name}` header, + does not have {article} "{name}" header, but it was """); } @@ -69,8 +84,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-my-header` header, - but it did contain the `x-my-header` header: ["some header"] + does not have an "x-my-header" header, + but it did contain the "x-my-header" header: ["some header"] HTTP-Response: 200 OK HTTP/1.1 @@ -92,7 +107,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - does not have an `x-my-header` header, + does not have an "x-my-header" header, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContent.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContent.Tests.cs index 7fdc063..a8e8bf6 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContent.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContent.Tests.cs @@ -8,6 +8,36 @@ public sealed partial class HasContent { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(response => response.HasContent("foo")))); + + await That(Act).Throws() + .WithMessage("""*whose Responses do not have a string content equal to "foo" for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasContent("bar"))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have a string content equal to "bar" for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { @@ -35,7 +65,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a string content equal to "other content", - but it was "some content", which differs at index 0: + but it had string content "some content", which differs at index 0: ↓ (actual) "some content" "other content" diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContentType.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContentType.Tests.cs index fcf4dda..65dba80 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContentType.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasContentType.Tests.cs @@ -9,6 +9,21 @@ public sealed class HasContentType { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasContentType("application/json"))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have a `Content-Type` header equal to "application/json" for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a `Content-Type` header equal to "text/content-type", - but it was "text/other-content-type", which differs at index 5: + but it had content type "text/other-content-type", which differs at index 5: ↓ (actual) "text/other-content-type" "text/content-type" diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.Tests.cs index 60e468f..993106d 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.Tests.cs @@ -8,6 +8,21 @@ public sealed partial class HasHeader { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasHeader("x-my-header"))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have an "x-my-header" header for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenExpectedIsNull_ShouldThrowArgumentNullException() { @@ -37,7 +52,7 @@ async Task Act() await That(Act).Throws() .WithMessage($""" Expected that subject - has {article} `{name}` header, + has {article} "{name}" header, but it was """); } @@ -55,7 +70,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header, + has an "x-my-header" header, but it did not contain the expected header HTTP-Response: @@ -90,7 +105,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header, + has an "x-my-header" header, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValueTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValueTests.cs index 90174c2..9cc856d 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValueTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValueTests.cs @@ -23,7 +23,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose value is equal to "some header", + has an "x-some-other-key" header whose value is equal to "some header", but it did not contain the expected header HTTP-Response: @@ -49,7 +49,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose value is equal to "some other header", + has an "x-my-header" header whose value is equal to "some other header", but the value was "some header", which differs at index 5: ↓ (actual) "some header" @@ -89,7 +89,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose value is empty, + has an "x-my-key" header whose value is empty, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValuesTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValuesTests.cs index 4f341fc..a8596e5 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValuesTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WhoseValuesTests.cs @@ -23,7 +23,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose values have exactly 3 items, + has an "x-some-other-key" header whose values have exactly 3 items, but it did not contain the expected header HTTP-Response: @@ -51,7 +51,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose values contain "some other header" at least once, + has an "x-my-header" header whose values contain "some other header" at least once, but the values did not contain it HTTP-Response: @@ -92,7 +92,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose values are empty, + has an "x-my-key" header whose values are empty, but it was """); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WithValueTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WithValueTests.cs index f56757b..191d39a 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WithValueTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasHeader.WithValueTests.cs @@ -23,7 +23,7 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-some-other-key` header whose value is equal to "some header", + has an "x-some-other-key" header whose value is equal to "some header", but it did not contain the expected header HTTP-Response: @@ -49,8 +49,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose value is equal to "some other header", - but the value was "some header", which differs at index 5: + has an "x-my-header" header whose value is equal to "some other header", + but it had header value "some header", which differs at index 5: ↓ (actual) "some header" "some other header" @@ -93,8 +93,8 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-header` header whose value is equal to "some other header", - but the header contained 2 values ["some header", "some other value"] + has an "x-my-header" header whose value is equal to "some other header", + but it had 2 header values ["some header", "some other value"] HTTP-Response: 200 OK HTTP/1.1 @@ -116,10 +116,50 @@ async Task Act() await That(Act).Throws() .WithMessage(""" Expected that subject - has an `x-my-key` header whose value is equal to "foo", + has an "x-my-key" header whose value is equal to "foo", but it was """); } } + + public sealed class NegatedWithValueTests + { + [Fact] + public async Task WhenHeaderExistsAndValueDiffers_ShouldSucceed() + { + HttpResponseMessage subject = ResponseBuilder + .WithHeader("x-my-header", "some header"); + + async Task Act() + => await That(subject) + .DoesNotComplyWith(it => it.HasHeader("x-my-header").WithValue("some other header")); + + await That(Act).DoesNotThrow(); + } + + [Fact] + public async Task WhenHeaderExistsAndValueIsEqual_ShouldFail() + { + HttpResponseMessage subject = ResponseBuilder + .WithHeader("x-my-header", "some header"); + + async Task Act() + => await That(subject).DoesNotComplyWith(it => it.HasHeader("x-my-header").WithValue("some header")); + + await That(Act).Throws() + .WithMessage(""" + Expected that subject + does not have an "x-my-header" header whose value is equal to "some header", + but it did contain the "x-my-header" header: ["some header"] and had header value "some header" + + HTTP-Response: + 200 OK HTTP/1.1 + x-my-header: some header + Content-Type: text/plain; charset=utf-8 + Content-Length: 0 + """) + .Because("the negation applies to the header as a whole, like the negated text of a `Whose`"); + } + } } } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.Tests.cs index 1802d41..6b3bbf5 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.Tests.cs @@ -8,6 +8,36 @@ public sealed partial class HasProblemDetailsContent { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(response => response.HasProblemDetailsContent()))); + + await That(Act).Throws() + .WithMessage("""*whose Responses do not have a ProblemDetails content for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithContent("foo"),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasProblemDetailsContent())); + + await That(Act).Throws() + .WithMessage("""*whose Responses have a ProblemDetails content with any type for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task ShouldCombineMultipleChecks() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithDetailTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithDetailTests.cs index 786fae9..7ef2287 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithDetailTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithDetailTests.cs @@ -8,6 +8,32 @@ public sealed partial class HasProblemDetailsContent { public sealed class WithDetailTests { + [Fact] + public async Task WhenDetailIsSpecifiedTwice_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithDetail("foo").WithDetail("bar"); + + await That(Act).Throws() + .WithMessage("WithDetail cannot be specified more than once.") + .Because("the second detail would silently replace the first one"); + } + + [Fact] + public async Task WhenDetailIsSpecifiedTwiceAroundStatus_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithDetail("foo").WithStatus(500).WithDetail("bar"); + + await That(Act).Throws() + .WithMessage("WithDetail cannot be specified more than once.") + .Because("the second detail would silently replace the first one"); + } + [Fact] public async Task WhenDetailIsNull_ShouldThrowArgumentNullException() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithInstanceTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithInstanceTests.cs index c879fb4..9df8c04 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithInstanceTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithInstanceTests.cs @@ -8,6 +8,32 @@ public sealed partial class HasProblemDetailsContent { public sealed class WithInstanceTests { + [Fact] + public async Task WhenInstanceIsSpecifiedTwice_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithInstance("foo").WithInstance("bar"); + + await That(Act).Throws() + .WithMessage("WithInstance cannot be specified more than once.") + .Because("the second instance would silently replace the first one"); + } + + [Fact] + public async Task WhenInstanceIsSpecifiedTwiceAroundStatus_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithInstance("foo").WithStatus(500).WithInstance("bar"); + + await That(Act).Throws() + .WithMessage("WithInstance cannot be specified more than once.") + .Because("the second instance would silently replace the first one"); + } + [Fact] public async Task WhenInstanceIsNull_ShouldThrowArgumentNullException() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithStatusTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithStatusTests.cs index fa8a32b..3cb82c3 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithStatusTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithStatusTests.cs @@ -9,34 +9,29 @@ public sealed partial class HasProblemDetailsContent public sealed class WithStatusTests { [Fact] - public async Task WhenCheckingStatusDifferentlyTwice_ShouldFail() + public async Task WhenStatusIsSpecifiedTwice_ShouldThrowInvalidOperationException() { - HttpResponseMessage subject = ResponseBuilder - .WithContent(""" - { - "type": "my-type", - "status": 500 - } - """); + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); async Task Act() => await That(subject).HasProblemDetailsContent().WithStatus(500).WithStatus(501); - await That(Act).Throws() - .WithMessage(""" - Expected that subject - has a ProblemDetails content with any type, status 500 and status 501, - but it had status 500 + await That(Act).Throws() + .WithMessage("WithStatus cannot be specified more than once.") + .Because("the second status would silently replace the first one"); + } - HTTP-Response: - 200 OK HTTP/1.1 - Content-Type: text/plain; charset=utf-8 - Content-Length: * - { - "type": "my-type", - "status": 500 - } - """).AsWildcard(); + [Fact] + public async Task WhenStatusIsSpecifiedTwiceAroundTitle_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithStatus(500).WithTitle("foo").WithStatus(501); + + await That(Act).Throws() + .WithMessage("WithStatus cannot be specified more than once.") + .Because("the second status would silently replace the first one"); } [Theory] @@ -84,7 +79,7 @@ public async Task WhenStatusMatches_ShouldSucceed() """); async Task Act() - => await That(subject).HasProblemDetailsContent().WithStatus(500).WithStatus(500); + => await That(subject).HasProblemDetailsContent().WithStatus(500); await That(Act).DoesNotThrow(); } diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithTitleTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithTitleTests.cs index 52358db..2953adc 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithTitleTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasProblemDetails.WithTitleTests.cs @@ -8,6 +8,32 @@ public sealed partial class HasProblemDetailsContent { public sealed class WithTitleTests { + [Fact] + public async Task WhenTitleIsSpecifiedTwice_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithTitle("foo").WithTitle("bar"); + + await That(Act).Throws() + .WithMessage("WithTitle cannot be specified more than once.") + .Because("the second title would silently replace the first one"); + } + + [Fact] + public async Task WhenTitleIsSpecifiedTwiceAroundStatus_ShouldThrowInvalidOperationException() + { + HttpResponseMessage subject = ResponseBuilder.WithContent("{}"); + + async Task Act() + => await That(subject).HasProblemDetailsContent().WithTitle("foo").WithStatus(500).WithTitle("bar"); + + await That(Act).Throws() + .WithMessage("WithTitle cannot be specified more than once.") + .Because("the second title would silently replace the first one"); + } + [Fact] public async Task WhenTitleIsNull_ShouldThrowArgumentNullException() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasRequestMessage.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasRequestMessage.Tests.cs index 50c0b8e..66d0b4b 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasRequestMessage.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasRequestMessage.Tests.cs @@ -36,7 +36,7 @@ await That(Act).Throws() .WithMessage(""" Expected that subject has a request message which has a string content equal to "bar", - but it was "foo", which differs at index 0: + but it had string content "foo", which differs at index 0: ↓ (actual) "foo" "bar" diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.DifferentToTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.DifferentToTests.cs index d511165..46c4cdf 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.DifferentToTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.DifferentToTests.cs @@ -9,6 +9,36 @@ public sealed partial class HasStatusCode { public sealed class DifferentToTests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(response => response.HasStatusCode().DifferentTo(HttpStatusCode.NotFound)))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have status code 404 NotFound for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasStatusCode().DifferentTo(HttpStatusCode.OK))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have status code different to 200 OK for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenStatusCodeDiffersFromExpected_ShouldSucceed() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.EqualToTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.EqualToTests.cs index 0a293f4..4744917 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.EqualToTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.EqualToTests.cs @@ -9,6 +9,36 @@ public sealed partial class HasStatusCode { public sealed class EqualToTests { + [Fact] + public async Task WhenMemberIsACollection_Negated_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.DoesNotComplyWith(response => response.HasStatusCode().EqualTo(HttpStatusCode.OK)))); + + await That(Act).Throws() + .WithMessage("""*whose Responses do not have status code 200 OK for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasStatusCode().EqualTo(HttpStatusCode.NotFound))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have status code 404 NotFound for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenFailing_ShouldIncludeRequestInMessage() { diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.SuccessTests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.SuccessTests.cs index 0c5e042..a25375f 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.SuccessTests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.SuccessTests.cs @@ -9,6 +9,20 @@ public sealed partial class HasStatusCode { public sealed class SuccessTests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder.WithStatusCode(HttpStatusCode.NotFound),]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasStatusCode().Success())); + + await That(Act).Throws() + .WithMessage("""*whose Responses have a success status code (2xx) for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } [Theory] [MemberData(nameof(SuccessStatusCodes), MemberType = typeof(ThatHttpResponseMessage))] public async Task WhenStatusCodeIsExpected_ShouldSucceed(HttpStatusCode statusCode) diff --git a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.Tests.cs b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.Tests.cs index fafb429..cce3013 100644 --- a/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatHttpResponseMessage.HasStatusCode.Tests.cs @@ -9,6 +9,21 @@ public sealed partial class HasStatusCode { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + HttpResponseMessage[] subjects = [ResponseBuilder,]; + + async Task Act() + => await That(new { Responses = subjects, }) + .Whose(x => x.Responses, items => items.All() + .ComplyWith(item => item.HasStatusCode(HttpStatusCode.NotFound))); + + await That(Act).Throws() + .WithMessage("""*whose Responses have status code 404 NotFound for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenFailing_ShouldIncludeRequestInMessage() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.DoesNotHaveDefaultPort.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.DoesNotHaveDefaultPort.Tests.cs index 63b3162..6a730ab 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.DoesNotHaveDefaultPort.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.DoesNotHaveDefaultPort.Tests.cs @@ -6,6 +6,21 @@ public sealed class DoesNotHaveDefaultPort { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.DoesNotHaveDefaultPort())); + + await That(Act).Throws() + .WithMessage("""*whose Uris do not have the default port for the used scheme for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.HasDefaultPort.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.HasDefaultPort.Tests.cs index fd2733d..62af8ff 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.HasDefaultPort.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.HasDefaultPort.Tests.cs @@ -6,6 +6,21 @@ public sealed class HasDefaultPort { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com:8080/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.HasDefaultPort())); + + await That(Act).Throws() + .WithMessage("""*whose Uris have the default port for the used scheme for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsAbsolute.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsAbsolute.Tests.cs index c23fcaa..3bd1cf3 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsAbsolute.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsAbsolute.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsAbsolute { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("/foo", UriKind.Relative),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsAbsolute())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are absolute URIs for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsFile.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsFile.Tests.cs index 23bd1a7..3f6a5b4 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsFile.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsFile.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsFile { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsFile())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are file URIs for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsLoopback.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsLoopback.Tests.cs index 87c24e0..b95a810 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsLoopback.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsLoopback.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsLoopback { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsLoopback())); + + await That(Act).Throws() + .WithMessage("""*whose Uris reference the local host for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsNotAbsolute.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsNotAbsolute.Tests.cs index a43e5e1..04a925f 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsNotAbsolute.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsNotAbsolute.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsNotAbsolute { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsNotAbsolute())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are not absolute URIs for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsNotFile.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsNotFile.Tests.cs index 3769369..d263f21 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsNotFile.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsNotFile.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsNotFile { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("file:///C:/foo"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsNotFile())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are not file URIs for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsNotLoopback.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsNotLoopback.Tests.cs index 915445e..3e77c71 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsNotLoopback.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsNotLoopback.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsNotLoopback { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("http://localhost/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsNotLoopback())); + + await That(Act).Throws() + .WithMessage("""*whose Uris do not reference the local host for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsNotUnc.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsNotUnc.Tests.cs index 16c9400..75fe9ae 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsNotUnc.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsNotUnc.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsNotUnc { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri(@"\\server\share"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsNotUnc())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are not UNC paths for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() { diff --git a/Tests/aweXpect.Web.Tests/ThatUri.IsUnc.Tests.cs b/Tests/aweXpect.Web.Tests/ThatUri.IsUnc.Tests.cs index 3f9a24d..16c76f9 100644 --- a/Tests/aweXpect.Web.Tests/ThatUri.IsUnc.Tests.cs +++ b/Tests/aweXpect.Web.Tests/ThatUri.IsUnc.Tests.cs @@ -6,6 +6,21 @@ public sealed class IsUnc { public sealed class Tests { + [Fact] + public async Task WhenMemberIsACollection_ShouldUsePluralVerb() + { + Uri[] subjects = [new Uri("https://awexpect.com/"),]; + + async Task Act() + => await That(new { Uris = subjects, }) + .Whose(x => x.Uris, items => items.All() + .ComplyWith(item => item.IsUnc())); + + await That(Act).Throws() + .WithMessage("""*whose Uris are UNC paths for all items,*""").AsWildcard() + .Because("the verb agrees with the plural member"); + } + [Fact] public async Task WhenSubjectIsNull_ShouldFail() {