/// <summary> /// Verifies the data service response. /// </summary> /// <param name="responseData">The expected data for the response.</param> /// <param name="response">The response to verify.</param> /// <param name="cachedOperationsFromResponse">The individual operation responses, pre-enumerated and cached</param> /// <exception cref="TaupoNotSupportedException"> /// When exception is expected in the response data /// </exception> public void VerifyDataServiceResponse(DataServiceResponseData responseData, DataServiceResponse response, IList <OperationResponse> cachedOperationsFromResponse) { ExceptionUtilities.CheckArgumentNotNull(responseData, "responseData"); ExceptionUtilities.CheckArgumentNotNull(response, "response"); ExceptionUtilities.CheckArgumentNotNull(cachedOperationsFromResponse, "cachedOperationsFromResponse"); ExceptionUtilities.CheckAllRequiredDependencies(this); this.Assert.AreEqual(responseData.IsBatchResponse, response.IsBatchResponse, "Verifying if it's a batch response."); this.Assert.AreEqual(responseData.BatchStatusCode, response.BatchStatusCode, "Verifying batch status code."); //// No batch headers verification //// Note: verifying order of operation responses as well as the content. this.Assert.AreEqual(responseData.Count, cachedOperationsFromResponse.Count, "Unexpected number of operation responses in data service response"); for (int responseOrder = 0; responseOrder < responseData.Count; responseOrder++) { var operationResponseData = responseData[responseOrder]; var currentResponse = cachedOperationsFromResponse[responseOrder]; ChangeOperationResponseData changeResponseData = operationResponseData as ChangeOperationResponseData; if (changeResponseData != null) { ChangeOperationResponse changeResponse = currentResponse as ChangeOperationResponse; this.Assert.IsNotNull(changeResponse, GetVerificationFailureMessage(responseOrder, "Unexpected type of the operation response.\r\nExpected: {0}\r\nActual: {1}", typeof(ChangeOperationResponse).FullName, currentResponse)); this.VerifyChangeOperationResponse(changeResponseData, changeResponse, responseOrder); } else { throw new TaupoNotSupportedException( GetVerificationFailureMessage(responseOrder, "Verification for the operation response data of type '{0}' is not supported by this verifier.", operationResponseData.GetType().FullName)); } } }
/// <summary> /// Verifies the data service response. /// </summary> /// <param name="responseData">The expected data for the response.</param> /// <param name="response">The response to verify.</param> /// <param name="cachedOperationsFromResponse">The individual operation responses, pre-enumerated and cached</param> /// <exception cref="TaupoNotSupportedException"> /// When exception is expected in the response data /// </exception> public void VerifyDataServiceResponse(DataServiceResponseData responseData, DataServiceResponse response, IList<OperationResponse> cachedOperationsFromResponse) { ExceptionUtilities.CheckArgumentNotNull(responseData, "responseData"); ExceptionUtilities.CheckArgumentNotNull(response, "response"); ExceptionUtilities.CheckArgumentNotNull(cachedOperationsFromResponse, "cachedOperationsFromResponse"); ExceptionUtilities.CheckAllRequiredDependencies(this); this.Assert.AreEqual(responseData.IsBatchResponse, response.IsBatchResponse, "Verifying if it's a batch response."); this.Assert.AreEqual(responseData.BatchStatusCode, response.BatchStatusCode, "Verifying batch status code."); //// No batch headers verification //// Note: verifying order of operation responses as well as the content. this.Assert.AreEqual(responseData.Count, cachedOperationsFromResponse.Count, "Unexpected number of operation responses in data service response"); for (int responseOrder = 0; responseOrder < responseData.Count; responseOrder++) { var operationResponseData = responseData[responseOrder]; var currentResponse = cachedOperationsFromResponse[responseOrder]; ChangeOperationResponseData changeResponseData = operationResponseData as ChangeOperationResponseData; if (changeResponseData != null) { ChangeOperationResponse changeResponse = currentResponse as ChangeOperationResponse; this.Assert.IsNotNull(changeResponse, GetVerificationFailureMessage(responseOrder, "Unexpected type of the operation response.\r\nExpected: {0}\r\nActual: {1}", typeof(ChangeOperationResponse).FullName, currentResponse)); this.VerifyChangeOperationResponse(changeResponseData, changeResponse, responseOrder); } else { throw new TaupoNotSupportedException( GetVerificationFailureMessage(responseOrder, "Verification for the operation response data of type '{0}' is not supported by this verifier.", operationResponseData.GetType().FullName)); } } }
private void UpdateResponse(DataServiceResponseData responseData, DescriptorData descriptor, HttpResponseData response) { var operationResponse = new ChangeOperationResponseData(descriptor); operationResponse.StatusCode = (int)response.StatusCode; foreach (var header in response.Headers) { operationResponse.Headers.Add(header.Key, header.Value); } responseData.Add(operationResponse); }
private void HandleBatchRequest(DataServiceResponseData responseData) { this.parent.Assert.AreEqual(1, this.httpQueue.Count, "Should only observe one request in $batch scenarios"); var batchRequest = (HttpRequestData)this.httpQueue.Peek().Key; var batchRequestPayload = this.parent.BatchDeserializer.DeserializeBatchRequest(batchRequest); this.parent.Assert.AreEqual(1, batchRequestPayload.Changesets.Count(), "Batch request payload should only have one changeset"); this.parent.Assert.AreEqual(0, batchRequestPayload.Operations.Count(), "Batch request payload should not contain any operations"); var expectedBatchHeaders = new HttpHeaderCollection() { Accept = MimeTypes.MultipartMixed, IfMatch = null, Prefer = null, HttpMethod = null, DataServiceVersion = DataServiceProtocolVersion.V4.ConvertToHeaderFormat() + ";" + HttpHeaders.NetFx, MaxDataServiceVersion = this.contextData.MaxProtocolVersion.ConvertToHeaderFormat() + ";" + HttpHeaders.NetFx, }; this.CompareHeaders(expectedBatchHeaders, batchRequest.Headers); var batchResponse = this.httpQueue.Dequeue().Value; responseData.BatchStatusCode = (int)batchResponse.StatusCode; responseData.BatchHeaders.AddRange(batchResponse.Headers); var batchResponsePayload = this.parent.BatchDeserializer.DeserializeBatchResponse(batchRequestPayload, batchResponse); this.parent.Assert.AreEqual(1, batchResponsePayload.Changesets.Count(), "Batch response payload should only have one changeset"); this.parent.Assert.AreEqual(0, batchResponsePayload.Operations.Count(), "Batch response payload should not contain any operations"); var requestChangeset = batchRequestPayload.Changesets.Single(); var responseChangeset = batchResponsePayload.Changesets.Single(); this.parent.Assert.AreEqual(requestChangeset.Count(), responseChangeset.Count(), "Unexpected number of response operations in changeset"); // TODO: report this better this.parent.Assert.IsTrue(requestChangeset.Operations.All(r => r is ODataRequest), "Some requests were not fully deserialized"); this.httpQueue = new Queue <KeyValuePair <IHttpRequest, HttpResponseData> >(requestChangeset.Operations.Zip(responseChangeset.Operations, (req, res) => new KeyValuePair <IHttpRequest, HttpResponseData>(req, res))); }
/// <summary> /// Calculates the expected response from DataServiceContext.SaveChanges based on the context data before saving changes. /// Assumes there are no errors in the response. /// </summary> /// <param name="dataBeforeSaveChanges">The data before saving changes.</param> /// <param name="options">The options for saving changes.</param> /// <param name="context">The DataServiceContext instance which is calling SaveChanges.</param> /// <returns><see cref="DataServiceResponseData"/> that expresses expectations for the response.</returns> public DataServiceResponseData CalculateSaveChangesResponseData(DataServiceContextData dataBeforeSaveChanges, SaveChangesOptions options, DSClient.DataServiceContext context) { ExceptionUtilities.CheckArgumentNotNull(dataBeforeSaveChanges, "dataBeforeSaveChanges"); ExceptionUtilities.CheckArgumentNotNull(context, "context"); DataServiceResponseData responseData = new DataServiceResponseData(); responseData.IsBatchResponse = options == SaveChangesOptions.Batch; bool hasChanges = false; // Note: ordering is important as changes should be processed in the order specified by user. foreach (DescriptorData descriptorData in dataBeforeSaveChanges.GetOrderedChanges()) { int statusCode = (int)HttpStatusCode.NoContent; var entityDescriptorData = descriptorData as EntityDescriptorData; if (entityDescriptorData != null) { if (entityDescriptorData.IsMediaLinkEntry && entityDescriptorData.DefaultStreamState == EntityStates.Modified) { responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); } if (descriptorData.State == EntityStates.Added) { statusCode = GetStatusCodeForInsert(context); if (entityDescriptorData.IsMediaLinkEntry) { responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); statusCode = GetStatusCodeForUpdate(context); } } else if (descriptorData.State == EntityStates.Modified) { statusCode = GetStatusCodeForUpdate(context); } else if (descriptorData.State != EntityStates.Deleted) { continue; } } var linkDescriptorData = descriptorData as LinkDescriptorData; if (linkDescriptorData != null && (linkDescriptorData.State == EntityStates.Added || linkDescriptorData.State == EntityStates.Modified)) { if (!linkDescriptorData.WillTriggerSeparateRequest()) { continue; } } responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); hasChanges = true; } if (responseData.IsBatchResponse) { responseData.BatchStatusCode = hasChanges ? (int)HttpStatusCode.Accepted : 0; } else { responseData.BatchStatusCode = -1; } return(responseData); }
private void HandleBatchRequest(DataServiceResponseData responseData) { this.parent.Assert.AreEqual(1, this.httpQueue.Count, "Should only observe one request in $batch scenarios"); var batchRequest = (HttpRequestData)this.httpQueue.Peek().Key; var batchRequestPayload = this.parent.BatchDeserializer.DeserializeBatchRequest(batchRequest); this.parent.Assert.AreEqual(1, batchRequestPayload.Changesets.Count(), "Batch request payload should only have one changeset"); this.parent.Assert.AreEqual(0, batchRequestPayload.Operations.Count(), "Batch request payload should not contain any operations"); var expectedBatchHeaders = new HttpHeaderCollection() { Accept = MimeTypes.MultipartMixed, IfMatch = null, Prefer = null, HttpMethod = null, DataServiceVersion = DataServiceProtocolVersion.V4.ConvertToHeaderFormat() + ";" + HttpHeaders.NetFx, MaxDataServiceVersion = this.contextData.MaxProtocolVersion.ConvertToHeaderFormat() + ";" + HttpHeaders.NetFx, }; this.CompareHeaders(expectedBatchHeaders, batchRequest.Headers); var batchResponse = this.httpQueue.Dequeue().Value; responseData.BatchStatusCode = (int)batchResponse.StatusCode; responseData.BatchHeaders.AddRange(batchResponse.Headers); var batchResponsePayload = this.parent.BatchDeserializer.DeserializeBatchResponse(batchRequestPayload, batchResponse); this.parent.Assert.AreEqual(1, batchResponsePayload.Changesets.Count(), "Batch response payload should only have one changeset"); this.parent.Assert.AreEqual(0, batchResponsePayload.Operations.Count(), "Batch response payload should not contain any operations"); var requestChangeset = batchRequestPayload.Changesets.Single(); var responseChangeset = batchResponsePayload.Changesets.Single(); this.parent.Assert.AreEqual(requestChangeset.Count(), responseChangeset.Count(), "Unexpected number of response operations in changeset"); // TODO: report this better this.parent.Assert.IsTrue(requestChangeset.Operations.All(r => r is ODataRequest), "Some requests were not fully deserialized"); this.httpQueue = new Queue<KeyValuePair<IHttpRequest, HttpResponseData>>(requestChangeset.Operations.Zip(responseChangeset.Operations, (req, res) => new KeyValuePair<IHttpRequest, HttpResponseData>(req, res))); }
/// <summary> /// Runs the save changes pipeline /// </summary> /// <returns>The response data to expect</returns> public DataServiceResponseData Run() { var responseData = new DataServiceResponseData(); bool isBatch = responseData.IsBatchResponse = this.options == SaveChangesOptions.Batch; if (!isBatch) { responseData.BatchStatusCode = -1; } // add the default stream descriptors into the ordered changes immediately before the entities they go with var orderedChanges = this.contextData.GetOrderedChanges() .SelectMany( d => { var entityDescriptor = d as EntityDescriptorData; if (entityDescriptor == null || !entityDescriptor.IsMediaLinkEntry) { return new[] { d }; } return new DescriptorData[] { entityDescriptor.DefaultStreamDescriptor, entityDescriptor } .Where(d2 => d2.State != EntityStates.Unchanged); }) .ToList(); if (orderedChanges.Count == 0) { this.parent.Assert.AreEqual(0, this.httpQueue.Count, "No requests should have been sent"); return responseData; } if (isBatch) { this.HandleBatchRequest(responseData); } var pendingUpdates = new List<Action>(); foreach (var change in orderedChanges) { var descriptor = change; var expectedRequest = this.parent.RequestCalculator.CalculateRequest(this.contextData, this.propertyValuesBeforeSave, descriptor, this.options); if (expectedRequest != null) { this.parent.Assert.AreNotEqual(0, this.httpQueue.Count, "Fewer requests were made than expected"); var pair = this.httpQueue.Dequeue(); var streamDescriptor = descriptor as StreamDescriptorData; if (streamDescriptor != null && streamDescriptor.Name == null) { descriptor = streamDescriptor.EntityDescriptor; } SetContentIDHeader(descriptor, expectedRequest, isBatch); bool tunnelling = this.contextData.UsePostTunneling; if (isBatch || expectedRequest.Verb == HttpVerb.Post) { tunnelling = false; } SetVerbTunnelling(expectedRequest, tunnelling); this.CompareRequest(expectedRequest, pair.Key); this.UpdateResponse(responseData, descriptor, pair.Value); if (!isBatch) { this.ApplyResponseAndUpdateState(descriptor, pair); } else { pendingUpdates.Add(() => this.ApplyResponseAndUpdateState(descriptor, pair)); } } } pendingUpdates.ForEach(a => a()); this.parent.Assert.AreEqual(0, this.httpQueue.Count, "More requests were made than expected"); return responseData; }
/// <summary> /// Calculates the expected response from DataServiceContext.SaveChanges based on the context data before saving changes. /// Assumes there are no errors in the response. /// </summary> /// <param name="dataBeforeSaveChanges">The data before saving changes.</param> /// <param name="options">The options for saving changes.</param> /// <param name="context">The DataServiceContext instance which is calling SaveChanges.</param> /// <returns><see cref="DataServiceResponseData"/> that expresses expectations for the response.</returns> public DataServiceResponseData CalculateSaveChangesResponseData(DataServiceContextData dataBeforeSaveChanges, SaveChangesOptions options, DSClient.DataServiceContext context) { ExceptionUtilities.CheckArgumentNotNull(dataBeforeSaveChanges, "dataBeforeSaveChanges"); ExceptionUtilities.CheckArgumentNotNull(context, "context"); DataServiceResponseData responseData = new DataServiceResponseData(); responseData.IsBatchResponse = options == SaveChangesOptions.Batch; bool hasChanges = false; // Note: ordering is important as changes should be processed in the order specified by user. foreach (DescriptorData descriptorData in dataBeforeSaveChanges.GetOrderedChanges()) { int statusCode = (int)HttpStatusCode.NoContent; var entityDescriptorData = descriptorData as EntityDescriptorData; if (entityDescriptorData != null) { if (entityDescriptorData.IsMediaLinkEntry && entityDescriptorData.DefaultStreamState == EntityStates.Modified) { responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); } if (descriptorData.State == EntityStates.Added) { statusCode = GetStatusCodeForInsert(context); if (entityDescriptorData.IsMediaLinkEntry) { responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); statusCode = GetStatusCodeForUpdate(context); } } else if (descriptorData.State == EntityStates.Modified) { statusCode = GetStatusCodeForUpdate(context); } else if (descriptorData.State != EntityStates.Deleted) { continue; } } var linkDescriptorData = descriptorData as LinkDescriptorData; if (linkDescriptorData != null && (linkDescriptorData.State == EntityStates.Added || linkDescriptorData.State == EntityStates.Modified)) { if (!linkDescriptorData.WillTriggerSeparateRequest()) { continue; } } responseData.Add(new ChangeOperationResponseData(descriptorData) { StatusCode = statusCode }); hasChanges = true; } if (responseData.IsBatchResponse) { responseData.BatchStatusCode = hasChanges ? (int)HttpStatusCode.Accepted : 0; } else { responseData.BatchStatusCode = -1; } return responseData; }
/// <summary> /// Runs the save changes pipeline /// </summary> /// <returns>The response data to expect</returns> public DataServiceResponseData Run() { var responseData = new DataServiceResponseData(); bool isBatch = responseData.IsBatchResponse = this.options == SaveChangesOptions.Batch; if (!isBatch) { responseData.BatchStatusCode = -1; } // add the default stream descriptors into the ordered changes immediately before the entities they go with var orderedChanges = this.contextData.GetOrderedChanges() .SelectMany( d => { var entityDescriptor = d as EntityDescriptorData; if (entityDescriptor == null || !entityDescriptor.IsMediaLinkEntry) { return(new[] { d }); } return(new DescriptorData[] { entityDescriptor.DefaultStreamDescriptor, entityDescriptor } .Where(d2 => d2.State != EntityStates.Unchanged)); }) .ToList(); if (orderedChanges.Count == 0) { this.parent.Assert.AreEqual(0, this.httpQueue.Count, "No requests should have been sent"); return(responseData); } if (isBatch) { this.HandleBatchRequest(responseData); } var pendingUpdates = new List <Action>(); foreach (var change in orderedChanges) { var descriptor = change; var expectedRequest = this.parent.RequestCalculator.CalculateRequest(this.contextData, this.propertyValuesBeforeSave, descriptor, this.options); if (expectedRequest != null) { this.parent.Assert.AreNotEqual(0, this.httpQueue.Count, "Fewer requests were made than expected"); var pair = this.httpQueue.Dequeue(); var streamDescriptor = descriptor as StreamDescriptorData; if (streamDescriptor != null && streamDescriptor.Name == null) { descriptor = streamDescriptor.EntityDescriptor; } SetContentIDHeader(descriptor, expectedRequest, isBatch); bool tunnelling = this.contextData.UsePostTunneling; if (isBatch || expectedRequest.Verb == HttpVerb.Post) { tunnelling = false; } SetVerbTunnelling(expectedRequest, tunnelling); this.CompareRequest(expectedRequest, pair.Key); this.UpdateResponse(responseData, descriptor, pair.Value); if (!isBatch) { this.ApplyResponseAndUpdateState(descriptor, pair); } else { pendingUpdates.Add(() => this.ApplyResponseAndUpdateState(descriptor, pair)); } } } pendingUpdates.ForEach(a => a()); this.parent.Assert.AreEqual(0, this.httpQueue.Count, "More requests were made than expected"); return(responseData); }