/// <summary>
 /// Verifies the given OData request/response pair
 /// </summary>
 /// <param name="request">The request to verify</param>
 /// <param name="response">The response to verify</param>
 public virtual void Verify(ODataRequest request, ODataResponse response)
 {
     ExceptionUtilities.CheckArgumentNotNull(request, "request");
     ExceptionUtilities.CheckArgumentNotNull(response, "response");
     ExceptionUtilities.CheckAllRequiredDependencies(this);
     ExceptionUtilities.CheckObjectNotNull(this.Logger, "Cannot run verifier without logger");
 }
Example #2
0
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            string expectedETag = this.CalculateExpectedETag(request, response);
            this.Verify(expectedETag, request, response);
        }
        /// <summary>
        /// Verifies that any navigation or relationship/association links in the payload are correct.
        /// </summary>
        /// <param name="request">The request that was sent</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            // Do not need to verify relationship links in metadata response. Skip it to avoid NullReferenceException in GetExpectedEntitySet call
            // TODO: Make the Json Light decision based on the request URI. Skip for now, since the links are not serialized by default.
            if (request.Uri.IsMetadata() || this.IsJsonLightResponse(response))
            {
                return;
            }

            // gather up all the entities to verify
            List<EntityInstance> entities = new List<EntityInstance>();
            if (response.RootElement.ElementType == ODataPayloadElementType.EntityInstance)
            {
                entities.Add(response.RootElement as EntityInstance);
            }
            else if (response.RootElement.ElementType == ODataPayloadElementType.EntitySetInstance)
            {
                foreach (EntityInstance ei in response.RootElement as EntitySetInstance)
                {
                    entities.Add(ei as EntityInstance);
                }
            }

            this.VerifyEntityLinks(request, response, entities);
        }
 /// <summary>
 /// Returns true if for all entity or entity set response payloads
 /// </summary>
 /// <param name="response">The response</param>
 /// <returns>Whether or not this verifier applies to the response</returns>
 public bool Applies(ODataResponse response)
 {
     return response.RootElement != null && (
         response.RootElement.ElementType == ODataPayloadElementType.EntityInstance
         || response.RootElement.ElementType == ODataPayloadElementType.EntitySetInstance
         || response.RootElement.ElementType == ODataPayloadElementType.LinkCollection);
 }
Example #5
0
        /// <summary>
        /// Checks that the response's status code matches the expected value
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            this.Assert(response.StatusCode == this.ExpectedStatusCode, string.Format(CultureInfo.InvariantCulture, "Expected status code '{0}', observed '{1}'", this.ExpectedStatusCode, response.StatusCode), request, response);
            
            this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Status code was '{0}'", this.ExpectedStatusCode);
        }
        /// <summary>
        /// Verifies response has 'X-Content-Type-Options' HTTP header
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            string noSniffResponseHeaderValue = null;
            bool noSniffExists = response.Headers.TryGetValue(HttpHeaders.XContentTypeOptions, out noSniffResponseHeaderValue);
            this.Assert(noSniffExists, HttpHeaders.XContentTypeOptions + " header was not found in the response", request, response);
            this.Assert(NoSniffValue.Equals(noSniffResponseHeaderValue), HttpHeaders.XContentTypeOptions + " header contained incorrect value " + noSniffResponseHeaderValue, request, response);
        }
        /// <summary>
        /// Verifies the given OData request/response pair
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            if (response.RootElement != null)
            {
                this.Validator.ValidateDateTimeFormatting(response.RootElement);
            }
        }
        /// <summary>
        /// Helper method for verifiers to make assertions
        /// </summary>
        /// <param name="condition">The condition to assert is true</param>
        /// <param name="message">The message to write to the log if the assertion fails</param>
        /// <param name="request">The request being verified</param>
        /// <param name="response">The response being verified</param>
        protected void Assert(bool condition, string message, ODataRequest request, ODataResponse response)
        {
            if (!condition)
            {
                ExceptionUtilities.CheckArgumentNotNull(message, "message");
                ExceptionUtilities.CheckArgumentNotNull(request, "request");
                ExceptionUtilities.CheckArgumentNotNull(response, "response");

                this.Logger.WriteLine(LogLevel.Error, message);
                this.ReportFailure(request, response);
                throw new ResponseVerificationException();
            }
        }
Example #9
0
 internal void Verify(string expectedETag, ODataRequest request, ODataResponse response)
 {
     string etagValue;
     bool hadETag = response.Headers.TryGetValue(HttpHeaders.ETag, out etagValue);
     if (expectedETag == null)
     {
         this.Assert(!hadETag, string.Format(CultureInfo.InvariantCulture, "Response contained unexpected ETag header with value '{0}'.", etagValue), request, response);
     }
     else
     {
         this.Assert(hadETag, string.Format(CultureInfo.InvariantCulture, "ETag header unexpectedly missing from response. Expected '{0}'.", expectedETag), request, response);
         this.Assert(expectedETag == etagValue, string.Format(CultureInfo.InvariantCulture, "ETag header value incorrect.\r\nExpected: '{0}'\r\nActual:   '{1}'", expectedETag, etagValue), request, response);
     }
 }
        /// <summary>
        /// Helper method for verifiers to report failure
        /// </summary>
        /// <param name="request">The request that failed verification</param>
        /// <param name="response">The response that failed verification</param>
        protected void ReportFailure(ODataRequest request, ODataResponse response)
        {
            ExceptionUtilities.CheckArgumentNotNull(request, "request");
            ExceptionUtilities.CheckArgumentNotNull(response, "response");

            if (this.OnReportingFailure != null)
            {
                this.OnReportingFailure(request, response);
            }

            this.Logger.WriteLine(LogLevel.Verbose, "Response verification failure");
            request.WriteToLog(this.Logger, LogLevel.Verbose);
            response.WriteToLog(this.Logger, LogLevel.Verbose);
        }
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            try
            {
                var expected = this.Evaluator.Evaluate(request.Uri);
                this.VerificationServices.ValidateResponsePayload(request.Uri, response, expected, this.maxProtocolVersion);
            }
            catch (Exception e)
            {
                this.ReportFailure(request, response);
                throw new ResponseVerificationException(e);
            }
        }
        /// <summary>
        /// Checks that the response's status code matches the expected value
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            ODataErrorPayload errorPayload;
            this.Assert(TryGetErrorPayloadFromResponse(response, this.FormatSelector, out errorPayload), "Expected an error payload", request, response);

            this.ExpectedErrorMessage.VerifyExceptionMessage(
                this.ResourceVerifier, 
                errorPayload, 
                (assertion, errorMessage) =>
                {
                    this.Assert(assertion, errorMessage, request, response);
                });
        }
        /// <summary>
        /// Checks that the response's status code matches the expected value
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            if (request.Uri.IsBatch())
            {
                throw new TaupoNotSupportedException("Batching isn't supported yet, when it is we need to iterate over sets of requests in batch and calculate the version");
            }

            var expectedDataServiceProtocolVersion = this.CalculateDataServiceProtocolVersion(request, response);
            
            string expectedProtocolVersion = expectedDataServiceProtocolVersion.ToString().Replace("V", string.Empty) + ".0;";

            string actualDataServiceVersionHeaderValue = response.Headers[HttpHeaders.DataServiceVersion];

            this.Assert(expectedProtocolVersion.Equals(actualDataServiceVersionHeaderValue), string.Format(CultureInfo.InvariantCulture, "Data Service Version Header error, Expected '{0}' Actual '{1}'", expectedProtocolVersion, actualDataServiceVersionHeaderValue), request, response);
        }
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            if (response.StatusCode == HttpStatusCode.NoContent)
            {
                this.Assert(
                    !response.Headers.ContainsKey(HttpHeaders.ContentType),
                    "Content-Type header should not be returned for the responses without content.",
                    request,
                    response);
            }
            else
            {
                // Verify the response Content-Type header exists and is consistent with the request Accept header
                string responseContentType;
                this.Assert(
                    response.Headers.TryGetValue(HttpHeaders.ContentType, out responseContentType),
                    "Response Content-Type header does not exist.",
                    request,
                    response);

                string acceptedContentType = this.RetrieveAcceptedContentType(request);
                
                // TODO: strict verification based on the type of the uri
                var acceptedMimeTypes = acceptedContentType.Split(',').Select(type => type.ToLowerInvariant()).ToList();

                this.Assert(
                    acceptedMimeTypes.Any(h => responseContentType.StartsWith(h, StringComparison.Ordinal))
                    || acceptedMimeTypes.Any(acceptType => acceptType == MimeTypes.Any),
                    "Response Content-Type header should be consistent with request Accept header.",
                    request,
                    response);

                if (responseContentType.StartsWith(MimeTypes.ApplicationJson, StringComparison.OrdinalIgnoreCase)) 
                {
                    this.Assert(
                        responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightNonStreaming, StringComparison.Ordinal) ||
                        responseContentType.StartsWith(MimeTypes.ApplicationJsonODataLightStreaming, StringComparison.Ordinal),
                        "JSON responses should be fully qualified",
                        request,
                        response);
                }
            }
        }
        /// <summary>
        /// Helper method for getting the error payload from the response. Handles a special case for media-resource operations which may contain errors despite not normally
        /// being deserialized as such.
        /// </summary>
        /// <param name="response">The current response</param>
        /// <param name="formatSelector">The format selector to use if the response needs to be deserialized</param>
        /// <param name="errorPayload">The error payload if one was found</param>
        /// <returns>Whether or not an error payload was found</returns>
        internal static bool TryGetErrorPayloadFromResponse(ODataResponse response, IProtocolFormatStrategySelector formatSelector, out ODataErrorPayload errorPayload)
        {
            ExceptionUtilities.CheckArgumentNotNull(response, "response");

            errorPayload = null;
            var payload = response.RootElement;
            if (payload == null)
            {
                return false;
            }

            if (payload.ElementType == ODataPayloadElementType.ODataErrorPayload)
            {
                errorPayload = (ODataErrorPayload)payload;
                return true;
            }

            // From here out, try to handle special case for streams which come back with error payloads and are not interpreted
            if (payload.ElementType != ODataPayloadElementType.PrimitiveValue)
            {
                return false;
            }

            var body = ((PrimitiveValue)payload).ClrValue as byte[];
            if (body == null)
            {
                return false;
            }

            var contentType = response.GetHeaderValueIfExists(HttpHeaders.ContentType);
            if (contentType == null)
            {
                return false;
            }

            // deserialize it
            ExceptionUtilities.CheckArgumentNotNull(formatSelector, "formatSelector");
            var formatForContentType = formatSelector.GetStrategy(contentType, null);
            var deserializer = formatForContentType.GetDeserializer();
            payload = deserializer.DeserializeFromBinary(body, new ODataPayloadContext { EncodingName = HttpUtilities.GetContentTypeCharsetOrNull(contentType) });

            errorPayload = payload as ODataErrorPayload;
            return errorPayload != null;
        }
        /// <summary>
        /// Verify the given OData request/response pair against all attached verifiers that apply
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            IDisposable scope = null;
            if (this.Context != null)
            {
                scope = this.Context.Begin(request);
            }

            try
            {
                foreach (var verifier in this.Verifiers)
                {
                    // make sure the verifier applies
                    var selective = verifier as ISelectiveResponseVerifier;
                    if (selective == null || (selective.Applies(request) && selective.Applies(response)))
                    {
                        try
                        {
                            verifier.Verify(request, response);
                        }
                        catch (ResponseVerificationException e)
                        {
                            // wrap and re-throw to preserve original call-stack. Ideally we would just let these through.
                            throw new ResponseVerificationException(e);
                        }
                        catch (Exception e)
                        {
                            this.Logger.WriteLine(LogLevel.Error, "Verifier '{0}' threw unexpected exception '{1}'", verifier, e.Message);
                            this.ReportFailure(request, response);
                            throw new ResponseVerificationException(e);
                        }
                    }
                }
            }
            finally
            {
                if (scope != null)
                {
                    scope.Dispose();
                }
            }
        }
        /// <summary>
        /// Verifies the given OData request/response pair using the delegate given at construction time.
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            try
            {
                this.verify(request, response);
            }
            catch (ResponseVerificationException e)
            {
                // wrap and re-throw to preserve original call-stack. Ideally we would just let these through.
                throw new ResponseVerificationException(e);
            }
            catch (Exception e)
            {
                this.ReportFailure(request, response);
                throw new ResponseVerificationException(e);
            }
        }
        /// <summary>
        /// Validates the data in the response payload based on the expected query value
        /// </summary>
        /// <param name="requestUri">The request uri</param>
        /// <param name="response">The response</param>
        /// <param name="expected">The expected query value</param>
        /// <param name="maxProtocolVersion">The max protocol version of the service</param>
        public void ValidateResponsePayload(ODataUri requestUri, ODataResponse response, QueryValue expected, DataServiceProtocolVersion maxProtocolVersion)
        {
            ExceptionUtilities.CheckObjectNotNull(requestUri, "requestUri");
            ExceptionUtilities.CheckObjectNotNull(response, "response");
            ExceptionUtilities.CheckObjectNotNull(expected, "expected");

            var expectedVersion = response.GetDataServiceVersion();
            this.Validator.ExpectedProtocolVersion = expectedVersion;
            this.Validator.ExpectedPayloadOptions = ODataPayloadOptions.None;

            string contentType;
            if (response.Headers.TryGetValue(HttpHeaders.ContentType, out contentType))
            {
                var strategy = this.FormatSelector.GetStrategy(contentType, requestUri);
                ExceptionUtilities.CheckObjectNotNull(strategy, "Could not get strategy for content type '{0}'", contentType);
                this.Validator.PrimitiveValueComparer = strategy.GetPrimitiveComparer();

                this.Validator.ExpectedPayloadOptions = this.ProtocolImplementationDetails.GetExpectedPayloadOptions(contentType, expectedVersion, requestUri);
            }

            this.ValidateAndPrintInfoOnError(requestUri, response.RootElement, expected);
        }
        /// <summary>
        /// Verifies the response payload type is consistent with the request uri
        /// </summary>
        /// <param name="request">The request to verify</param>
        /// <param name="response">The response to verify</param>
        public override void Verify(ODataRequest request, ODataResponse response)
        {
            base.Verify(request, response);

            var expectedPayloadType = request.Uri.GetExpectedPayloadType();

            // Posting to entity set expects entity instance in response, but posting to service operation response should be consistent with its definition
            if (request.Verb == HttpVerb.Post && request.Uri.IsEntitySet() && (!request.Uri.IsWebInvokeServiceOperation()) && (!request.Uri.IsAction()))
            {
                expectedPayloadType = ODataPayloadElementType.EntityInstance;
            }

            string message = null;
            if (response.RootElement.ElementType != expectedPayloadType)
            {
                message = @"Response payload type did not match. 
Expected: '{0}'
Actual:   '{1}'";
                message = string.Format(CultureInfo.InvariantCulture, message, expectedPayloadType, response.RootElement.ElementType);
            }

            this.Assert(message == null, message, request, response);
        }
 /// <summary>
 /// Return true for all Atom+Xml responses
 /// </summary>
 /// <param name="response">The response that might need to be verified</param>
 /// <returns>Whether or not this verifier applies to the response</returns>
 public bool Applies(ODataResponse response)
 {
     return response.StatusCode != HttpStatusCode.NoContent;
 }
        /// <summary>
        /// Verify the relationship link is as expected.
        /// </summary>
        /// <param name="navigation">navigation property instance for the navigation link</param>
        /// <param name="associationLink">relationship link to verify</param>
        /// <param name="expectedAssociationUri">expected link value</param>
        /// <param name="request">The request needed for error report.</param>
        /// <param name="response">The response to check the content type.</param>
        private void VerifyAssociationLink(NavigationPropertyInstance navigation, DeferredLink associationLink, string expectedAssociationUri, ODataRequest request, ODataResponse response)
        {
            bool linkShouldBeNull = false;
            if (!this.IsAtomResponse(response) && navigation.IsExpanded)
            {
                var expandedValue = ((ExpandedLink)navigation.Value).ExpandedElement;
                linkShouldBeNull = expandedValue == null;
            }

            if (linkShouldBeNull)
            {   
                // json navigations are null, then there are no links
                if (associationLink != null)
                {
                    this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Expected association link == null, observed '{0}'", associationLink.UriString);
                    this.ReportFailure(request, response);
                    throw new ResponseVerificationException();
                }
            }
            else
            {
                this.VerifyLinkUriValue(associationLink.UriString, expectedAssociationUri, request, response);

                // extra verifications for atom payload
                if (this.IsAtomResponse(response))
                {
                    this.VerifyAtomAssociationLinkTypeAttribute(associationLink, request, response);
                    this.VerifyAtomTitleAttribute(navigation, associationLink, request, response);
                }
            }
        }
        /// <summary>
        /// Verify that the type attribute of relationship link is not null and has the expected value "application/xml".
        /// </summary>
        /// <param name="link">The navigation link to verify.</param>
        /// <param name="request">The request needed for error report.</param>
        /// <param name="response">The response needed for error report.</param>
        private void VerifyAtomAssociationLinkTypeAttribute(ODataPayloadElement link, ODataRequest request, ODataResponse response)
        {
            ContentTypeAnnotation typeAnnotation = link.Annotations.OfType<ContentTypeAnnotation>().SingleOrDefault();
            string expectedTypeAttributeValue = "application/xml";

            if (typeAnnotation == null || string.Compare(typeAnnotation.Value, expectedTypeAttributeValue, StringComparison.CurrentCulture) != 0)
            {
                this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Expected relationship link type attribute '{0}', observed '{1}'", expectedTypeAttributeValue, typeAnnotation == null ? "null" : typeAnnotation.Value);
                this.ReportFailure(request, response);
                throw new ResponseVerificationException();
            }
        }
 /// <summary>
 /// Verify that the title attribute of navigation/relationship link is not null and has the value consistent with the navigation property name.
 /// </summary>
 /// <param name="navigation">navigation property instance for the navigation link</param>
 /// <param name="link">The navigation/relationship link to verify.</param>
 /// <param name="request">The request needed for error report.</param>
 /// <param name="response">The response needed for error report.</param>
 private void VerifyAtomTitleAttribute(NavigationPropertyInstance navigation, ODataPayloadElement link, ODataRequest request, ODataResponse response)
 {
     TitleAnnotation titleAnnotation = link.Annotations.OfType<TitleAnnotation>().SingleOrDefault();
     if (titleAnnotation == null || string.Compare(titleAnnotation.Value, navigation.Name, StringComparison.CurrentCulture) != 0)
     {
         this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Expected navigation property Title attribute '{0}', observed '{1}'", navigation.Name, titleAnnotation == null ? "null" : titleAnnotation.Value);
         this.ReportFailure(request, response);
         throw new ResponseVerificationException();
     }
 }
 /// <summary>
 /// Verify that the navigation/relationship link value is consistent with expected value.
 /// </summary>
 /// <param name="link">The navigation/relationship link to verify.</param>
 /// <param name="expectedLinkValue">The expected link.</param>
 /// <param name="request">The request needed for error report.</param>
 /// <param name="response">The response needed for error report.</param>
 private void VerifyLinkUriValue(string link, string expectedLinkValue, ODataRequest request, ODataResponse response)
 {
     if (link == null ||
         !link.Equals(expectedLinkValue, StringComparison.Ordinal))
     {
         this.Logger.WriteLine(LogLevel.Error, CultureInfo.InvariantCulture, "Expected link {0}, observed {1}", expectedLinkValue, link == null ? "Link not Found." : link);
         this.ReportFailure(request, response);
         throw new ResponseVerificationException();
     }
 }
        /// <summary>
        /// Verify that the type attribute of navigation link is not null and has the expected value.
        /// </summary>
        /// <param name="link">The relationship link to verify.</param>
        /// <param name="request">The request needed for error report.</param>
        /// <param name="response">The response needed for error report.</param>
        private void VerifyAtomNavigationLinkTypeAttribute(ODataPayloadElement link, ODataRequest request, ODataResponse response)
        {
            ContentTypeAnnotation typeAnnotation = link.Annotations.OfType<ContentTypeAnnotation>().SingleOrDefault();

            if (typeAnnotation == null ||
                (!typeAnnotation.Value.Equals("application/atom+xml;type=feed", StringComparison.Ordinal) &&
                !typeAnnotation.Value.Equals("application/atom+xml;type=entry", StringComparison.Ordinal)))
            {
                this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Expected navigation link type attribute application/atom+xml;type=feed or application/atom+xml;type=entry, observed '{0}'", typeAnnotation == null ? "null" : typeAnnotation.Value);
                this.ReportFailure(request, response);
                throw new ResponseVerificationException();
            }
        }
 /// <summary>
 /// Returns true if response content is Json Light format.
 /// </summary>
 /// <param name="response">The response to check the content type.</param>
 /// <returns>True if the content type is Json Light, otherwise False.</returns>
 private bool IsJsonLightResponse(ODataResponse response)
 {
     string contentType = response.Headers[HttpHeaders.ContentType];
     return contentType.StartsWith(MimeTypes.ApplicationJsonODataLightNonStreaming, StringComparison.CurrentCulture) ||
            contentType.StartsWith(MimeTypes.ApplicationJsonODataLightStreaming, StringComparison.CurrentCulture);
 }    
 /// <summary>
 /// Returns true if response content is Atom feed/entry.
 /// </summary>
 /// <param name="response">The response to check the content type.</param>
 /// <returns>True if the content type is Atom, otherwise False.</returns>
 private bool IsAtomResponse(ODataResponse response)
 {
     return response.Headers[HttpHeaders.ContentType].StartsWith(MimeTypes.ApplicationAtomXml, StringComparison.CurrentCulture);
 }    
        /// <summary>
        /// Verify the expanded navigation link is as expected.
        /// </summary>
        /// <param name="navigation">expanded navigation link to verify</param>
        /// <param name="expectedNavigationUri">expected link value</param>
        /// <param name="request">The request needed for error report.</param>
        /// <param name="response">The response to check the content type.</param>
        private void VerifyNavigationExpandedLink(NavigationPropertyInstance navigation, string expectedNavigationUri, ODataRequest request, ODataResponse response)
        {
            if (this.IsAtomResponse(response))
            {
                this.VerifyNavigationLink(navigation, expectedNavigationUri, request, response);
            }
            else
            {
                // JSON never has navigation links for expanded elements
                string expandedLinkUri = ((ExpandedLink)navigation.Value).UriString;
                if (expandedLinkUri != null)
                {
                    this.Logger.WriteLine(LogLevel.Verbose, CultureInfo.InvariantCulture, "Expected expanded link uri == null, observed {0}", expandedLinkUri);
                    this.ReportFailure(request, response);
                    throw new ResponseVerificationException();
                }
            }

            List<EntityInstance> expandedEntities = new List<EntityInstance>();
            ODataPayloadElement expandedElement = ((ExpandedLink)navigation.Value).ExpandedElement;
            if (expandedElement != null)
            {
                EntityInstance entityInstance = expandedElement as EntityInstance;
                if (entityInstance != null)
                {
                    expandedEntities.Add(entityInstance);
                }
                else
                {
                    EntitySetInstance entitySetInstance = expandedElement as EntitySetInstance;

                    foreach (EntityInstance ei in entitySetInstance)
                    {
                        expandedEntities.Add(ei);
                    }
                }

                this.VerifyEntityLinks(request, response, expandedEntities);
            }
        }
        /// <summary>
        /// Verify the navigation link is as expected.
        /// </summary>
        /// <param name="navigation">navigation link to verify</param>
        /// <param name="expectedNavigationUri">expected link value</param>
        /// <param name="request">The request needed for error report.</param>
        /// <param name="response">The response to check the content type.</param>
        private void VerifyNavigationLink(NavigationPropertyInstance navigation, string expectedNavigationUri, ODataRequest request, ODataResponse response)
        {
            if (navigation.IsExpanded)
            {
                this.VerifyLinkUriValue(((ExpandedLink)navigation.Value).UriString, expectedNavigationUri, request, response);               
            }
            else
            {
                this.VerifyLinkUriValue(((DeferredLink)navigation.Value).UriString, expectedNavigationUri, request, response);
            }

            // extra verifications for atom payload
            if (this.IsAtomResponse(response))
            {
                this.VerifyAtomNavigationLinkTypeAttribute(navigation.Value, request, response);
                this.VerifyAtomTitleAttribute(navigation, navigation.Value, request, response);
            }
        }
        /// <summary>
        /// Verifies that any navigation or relationship/association links in the entities are correct.
        /// </summary>
        /// <param name="request">The request that was sent</param>
        /// <param name="response">The response to verify</param>
        /// <param name="entities">List of entities to verify links</param>
        private void VerifyEntityLinks(ODataRequest request, ODataResponse response, List<EntityInstance> entities)
        {
            // service behavior for telling whether or not the links should be there
            var serviceBehavior = this.model.GetDefaultEntityContainer().GetDataServiceBehavior();

            foreach (var entity in entities)
            {
                foreach (var navigation in entity.Properties.OfType<NavigationPropertyInstance>())
                {
                    string expectedNavigationUri = this.BuildExpectedNavigationUri(entity, navigation);
                    
                    // verify navigation link
                    if (navigation.Value.ElementType == ODataPayloadElementType.DeferredLink)
                    {
                        this.VerifyNavigationLink(navigation, expectedNavigationUri, request, response);
                    }
                    else
                    {
                        this.VerifyNavigationExpandedLink(navigation, expectedNavigationUri, request, response);
                    }

                    // verify relationship link
                    var associationLink = navigation.AssociationLink;
                    string expectedAssociationUri = this.BuildExpectedAssociationUri(entity, navigation);
                    if (serviceBehavior.IncludeAssociationLinksInResponse == true)
                    {
                        this.VerifyAssociationLink(navigation, associationLink, expectedAssociationUri, request, response);
                    }
                    else if (associationLink != null)
                    {
                        this.Logger.WriteLine(LogLevel.Verbose, "Property '{0}' had association link '{1}'", navigation.Name, associationLink);
                        this.Logger.WriteLine(LogLevel.Error, "Association link unexpectedly non-null for current service configuration");
                        this.ReportFailure(request, response);
                        throw new ResponseVerificationException();
                    }
                }
            }
        }