/// <summary> /// Verifies the descriptor after execute. /// </summary> /// <param name="continuation">The continuation.</param> /// <param name="dataContext">The data context.</param> /// <param name="entityPayloads">The list of entities</param> /// <param name="expectedValue">The expected value.</param> protected void VerifyDescriptorAfterExecute(IAsyncContinuation continuation, DataServiceContext dataContext, IEnumerable <object> entityPayloads, QueryValue expectedValue) { // If no streams were generated as part of the test initialization then this block of verification is needed if (this.SkipGenerateNamedStream) { continuation.Continue(); } else { object[] entities = entityPayloads.ToArray(); if (this.DataProviderSettings.UsePayloadDrivenVerification) { expectedValue = this.baselineQueryValue; } var expectedEntities = expectedValue as QueryCollectionValue; QueryStructuralValue element; if (expectedEntities == null) { continuation.Continue(); } else { AsyncHelpers.AsyncForEach( expectedEntities.Elements.ToList(), continuation, (qv, entityContinuation) => { // NOTE: The results could be a list of AnonTypes at which point these wouldn't be have descriptors so // no need to verify element = qv as QueryStructuralValue; var queryEntityType = element.Type as QueryEntityType; if (queryEntityType == null) { entityContinuation.Continue(); } else { var queryEntityValue = element as QueryEntityValue; QueryKeyStructuralValue key = queryEntityValue.Key(); // This handles the expand scenario (Orders(1)?$expand=Customer) where the entity in the list doesn't have a corresponding QueryStructuralValue object entity = entities.Where(upe => queryEntityType.ClrType.IsAssignableFrom(upe.GetType()) && queryEntityType.GetEntityInstanceKey(upe).Equals(key)).FirstOrDefault(); if (entity == null) { entityContinuation.Continue(); } else { EntityDescriptor ed = dataContext.GetEntityDescriptor(entity); var streamProperties = queryEntityType.Properties.Where(p => p.IsStream()).ToList(); // intentionally include the default stream int expectedStreamDescriptorCount = streamProperties.Count(p => p.Name != AstoriaQueryStreamType.DefaultStreamPropertyName); this.Assert.AreEqual(expectedStreamDescriptorCount, ed.StreamDescriptors.Count, "Entity descriptor had unexpected number of stream descriptors"); AsyncHelpers.AsyncForEach( streamProperties, entityContinuation, (streamProperty, streamDescriptorContinuation) => { if (streamProperty.Name == AstoriaQueryStreamType.DefaultStreamPropertyName) { this.VerifyDefaultStream(dataContext, element, ed, streamDescriptorContinuation); } else { this.VerifyNamedStreams(dataContext, element, streamProperty, ed, streamDescriptorContinuation); } }); } } }); } } }
/// <summary> /// Compares Collections, optimizes entity collection comparisons for better error messages or defers to the base /// </summary> /// <param name="expected">Expected Value</param> /// <param name="actualElements">Actual Elements to compare against</param> /// <param name="path">Path of the elements</param> /// <param name="shouldThrow">Should throw on error or not</param> /// <param name="comparisonSkippedForAnyElement">Skip for particular comparisons</param> /// <returns>A Comparison result</returns> protected override ComparisonResult CompareCollections(QueryCollectionValue expected, IEnumerable <object> actualElements, string path, bool shouldThrow, out bool comparisonSkippedForAnyElement) { comparisonSkippedForAnyElement = false; QueryEntityType queryEntityType = expected.Type.ElementType as QueryEntityType; if (queryEntityType != null) { List <QueryProperty> keyProperties = queryEntityType.Properties.Where(p => p.IsPrimaryKey).ToList(); ExceptionUtilities.Assert(queryEntityType.Properties.Where(p => p.IsPrimaryKey).Count() > 0, "QueryEntityType '{0}' must have a Primary key defined in order to compare using primary keys", queryEntityType.EntityType.FullName); List <object> unprocessableElements = actualElements.Where(e => !queryEntityType.ClrType.IsAssignableFrom(e.GetType())).ToList(); if (unprocessableElements.Count > 0) { string errorDetails = string.Join(", ", unprocessableElements.Select(o => string.Format(CultureInfo.InvariantCulture, "Expected object '{0}' to be assignable to '{1}'", o.GetType().FullName, queryEntityType.ClrType.FullName)).ToArray()); this.ThrowOrLogError(shouldThrow, "There are '{0}' elements that are unprocessable, Details '{1}'", unprocessableElements.Count, errorDetails); return(ComparisonResult.Failure); } List <object> unprocessedElements = actualElements.Where(e => queryEntityType.ClrType.IsAssignableFrom(e.GetType())).ToList(); foreach (QueryStructuralValue queryStructuralValue in expected.Elements) { var queryEntityValue = queryStructuralValue as QueryEntityValue; ExceptionUtilities.CheckObjectNotNull(queryEntityValue, "Expected Collection element to be a 'QueryEntityValue', got a '{0}' instead", queryStructuralValue); ExceptionUtilities.CheckObjectNotNull(queryEntityValue.IsNull, "Expected Collection of QueryEntityValues to not contain a QueryEntityValue that is null '{0}'", queryEntityValue.ToString()); // Note: We could fix this limitation by simply using the normal unsorted code path instead in these cases but no point implementing this unless its needed. ExceptionUtilities.CheckObjectNotNull(keyProperties.Select(kp => queryEntityValue.GetScalarValue(kp.Name).IsNull).Any(), "Error: QueryEntityValue must have key values defined"); QueryKeyStructuralValue key = queryEntityValue.Key(); object entityInstance = unprocessedElements.Where(upe => queryEntityType.GetEntityInstanceKey(upe).Equals(key)).SingleOrDefault(); if (entityInstance == null) { this.ThrowOrLogError(shouldThrow, "Cannot find actual EntityInstance that is assignable to type '{0}' with key '{1}' in expected collection '{2}'", queryEntityType.EntityType.FullName, key.GetDebugKeyString(), expected); return(ComparisonResult.Failure); } else { unprocessedElements.Remove(entityInstance); string childPath = string.Format(CultureInfo.InvariantCulture, "{0}.CollectionItem(ElementType='{1}',Key='{2}')", path, queryEntityType.EntityType.FullName, key.GetDebugKeyString()); ComparisonResult innerResult = this.Compare(queryEntityValue, entityInstance, childPath, false); if (innerResult == ComparisonResult.Failure) { this.DisplayLog(); this.ThrowOrLogError(shouldThrow, "Comparision of EntityInstance in path '{0}' failed, see log for details", childPath, key.GetDebugKeyString(), expected); return(ComparisonResult.Failure); } } } if (unprocessedElements.Count > 0) { string errorDetails = string.Join(", ", unprocessedElements.Select(o => string.Format(CultureInfo.InvariantCulture, "Actual EntityInstance of type '{0}' with key '{1}' is not contained in expected collection '{2}'", o.GetType().FullName, queryEntityType.GetEntityInstanceKey(o).GetDebugKeyString(), expected)).ToArray()); this.ThrowOrLogError(shouldThrow, "There are '{0}' elements that are unprocessable, Details '{1}'", unprocessedElements.Count, errorDetails); return(ComparisonResult.Failure); } return(ComparisonResult.Success); } else { return(base.CompareCollections(expected, actualElements, path, shouldThrow, out comparisonSkippedForAnyElement)); } }