public void GetResultForMutating__when__PUT_IfMatch_and_row_has_matching_ETag__then__precondition_Pass()
        {
            byte[] rowVersion = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
            string eTag;

            Assert.True(ETagUtility.TryCreate(rowVersion, out eTag));

            var request = new FakeHttpRequest()
            {
                Method      = HttpMethods.Put,
                FakeHeaders = new HeaderDictionary(new Dictionary <string, Microsoft.Extensions.Primitives.StringValues>()
                {
                    { "If-Match", (ETagUtility.FormatStandard(eTag)) }
                })
            };

            var rowWithMatchingETag = new ServerSideEntity()
            {
                RowVersion = rowVersion
            };

            var executor = new HttpPreconditionExecutor(null);

            // Act

            var result = executor.GetResult(request, PreconditionResult.BadRequest, rowWithMatchingETag);

            // Assert

            Assert.NotNull(result);
            Assert.Equal(PreconditionResultStatus.Passed, result.Status);
            Assert.Equal(0, result.StatusCode);
        }
        /// <summary>
        /// Gets the result for a HTTP method that doesn't change server side state.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="defaultResult">The default result.</param>
        /// <param name="localInformation">The local information.</param>
        /// <returns>PreconditionResult.</returns>
        protected virtual PreconditionResult GetResultForNonMutating(HttpRequest request, PreconditionResult defaultResult, IPreconditionInformation localInformation)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (defaultResult == null)
            {
                throw new ArgumentNullException(nameof(defaultResult));
            }

            if (localInformation == null)
            {
                throw new ArgumentNullException(nameof(localInformation));
            }

            var httpRequestHeaders = request.GetTypedHeaders();

            // Predicate code.

            string localETag;

            if (ETagUtility.TryCreate(localInformation, out localETag))
            {
                localETag = ETagUtility.FormatStandard(localETag);

                // Favours eTags over timestamps.
                //
                if (httpRequestHeaders.IfNoneMatch != null && httpRequestHeaders.IfNoneMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfNoneMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-None-Match: false");

                            return(PreconditionResult.PreconditionFailedNotModified); // As soon as one matches, we've failed (if *none* match).
                        }
                    }

                    _logger?.LogInformation("Precondition If-None-Match: true");

                    return(PreconditionResult.PreconditionPassed);
                }

                // Falls through to try modified stamps.
                //
                if (httpRequestHeaders.IfModifiedSince.HasValue && localInformation.ModifiedOn > DateTimeOffset.MinValue)
                {
                    DateTimeOffset clientTimestamp = httpRequestHeaders.IfModifiedSince.Value;

                    _logger?.LogTrace($"Precondition If-None-Match: client {clientTimestamp} vs local {localInformation.ModifiedOn}");

                    if (IsTimeSignificantlyGreaterThan(clientTimestamp, localInformation.ModifiedOn))
                    {
                        _logger?.LogInformation("Precondition If-Modified-Since: true");

                        return(PreconditionResult.PreconditionPassed);
                    }
                    else
                    {
                        _logger?.LogInformation("Precondition If-Modified-Since: false");

                        return(PreconditionResult.PreconditionFailedNotModified);
                    }
                }

                _logger?.LogInformation("Precondition headers not found: " + defaultResult.ToString());
            }
            else
            {
                _logger?.LogInformation("Local version data not found: " + defaultResult.ToString());
            }

            _logger?.LogInformation("The information required to determine precondition is not present. Defaulting to HTTP " + defaultResult.StatusCode);

            return(defaultResult);
        }
        /// <summary>
        /// Gets the result for a HTTP method that changes server side state.
        /// </summary>
        /// <param name="request">The request.</param>
        /// <param name="defaultResult">The default result.</param>
        /// <param name="localInformation">The server side information.</param>
        /// <returns>PreconditionResult.</returns>
        /// <exception cref="System.NotSupportedException">
        /// The wildcard ETag conditional PUT|DELETE is not supported.
        /// or
        /// The wildcard ETag conditional PUT|DELETE is not supported.
        /// </exception>
        protected virtual PreconditionResult GetResultForMutating(HttpRequest request, PreconditionResult defaultResult, IPreconditionInformation localInformation)
        {
            if (request == null)
            {
                throw new ArgumentNullException(nameof(request));
            }

            if (defaultResult == null)
            {
                throw new ArgumentNullException(nameof(defaultResult));
            }

            if (localInformation == null)
            {
                throw new ArgumentNullException(nameof(localInformation));
            }

            var httpRequestHeaders = request.GetTypedHeaders();

            // Predicate code.

            string localETag;

            if (ETagUtility.TryCreate(localInformation, out localETag))
            {
                localETag = ETagUtility.FormatStandard(localETag);

                // Favours eTags over timestamps.
                //
                if (httpRequestHeaders.IfMatch != null && httpRequestHeaders.IfMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == "*")
                        {
                            throw new NotSupportedException("The wildcard ETag conditional PUT|DELETE is not supported.");
                        }

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-Match: true");

                            return(PreconditionResult.PreconditionPassed);
                        }
                    }

                    _logger?.LogInformation("Precondition If-Match: false");

                    return(PreconditionResult.PreconditionFailed);
                }

                if (httpRequestHeaders.IfNoneMatch != null && httpRequestHeaders.IfNoneMatch.Any())
                {
                    foreach (var eTag in httpRequestHeaders.IfNoneMatch)
                    {
                        _logger?.LogTrace($"Precondition If-None-Match: client {eTag} vs local {localETag}");

                        if (eTag.Tag == "*")
                        {
                            throw new NotSupportedException("The wildcard ETag conditional PUT|DELETE is not supported.");
                        }

                        if (eTag.Tag == localETag)
                        {
                            _logger?.LogInformation("Precondition If-None-Match: false");

                            return(PreconditionResult.PreconditionFailed); // As soon as one matches, we've failed (if *none* match).
                        }
                    }

                    _logger?.LogInformation("Precondition If-None-Match: true");

                    return(PreconditionResult.PreconditionPassed);
                }

                // Falls through to try modified stamps, note: *Un*modified since.
                //
                if (httpRequestHeaders.IfUnmodifiedSince.HasValue && localInformation.ModifiedOn > DateTimeOffset.MinValue)
                {
                    DateTimeOffset clientTimestamp = httpRequestHeaders.IfUnmodifiedSince.Value;

                    _logger?.LogTrace($"Precondition If-Unmodified-Since: client {clientTimestamp} vs local {localInformation.ModifiedOn}");

                    // Pass, only if the server resource has not been modified since the specified date/time, i.e. if the timestamp
                    // on the server's copy matches that which the client has.
                    //
                    if (AreTimesAlmostEqual(clientTimestamp, localInformation.ModifiedOn))
                    {
                        _logger?.LogInformation("Precondition If-Unmodified-Since: true");

                        return(PreconditionResult.PreconditionPassed);
                    }
                    else
                    {
                        _logger?.LogInformation("Precondition If-Unmodified-Since: false");

                        return(PreconditionResult.PreconditionFailed);
                    }
                }

                _logger?.LogInformation("Precondition headers not found: " + defaultResult.ToString());
            }
            else
            {
                _logger?.LogInformation("Local version data not found: " + defaultResult.ToString());
            }

            _logger?.LogInformation("The information required to determine precondition is not present. Defaulting to HTTP " + defaultResult.StatusCode);

            return(defaultResult);
        }