From c0ebfb02cd5ea60e6e77ee48433a5bf23f14d77e Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Tue, 16 Oct 2018 13:55:52 -0400 Subject: [PATCH 1/2] Added support for following: 1 - Be able to retrieve HTTP response header to run the validation. 2 - When reading response headers, ensure to read content headers as well as the response headers. --- src/RA/ExecutionContext.cs | 23 +++++++++++++++++++---- src/RA/ResponseContext.cs | 15 +++++++++++++-- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/RA/ExecutionContext.cs b/src/RA/ExecutionContext.cs index d5c97d0..1947d9c 100644 --- a/src/RA/ExecutionContext.cs +++ b/src/RA/ExecutionContext.cs @@ -293,16 +293,30 @@ private async Task ExecuteCall() private ResponseContext BuildFromResponse(HttpResponseMessageWrapper result) { // var content = AsyncContext.Run(async () => await result.Response.Content.ReadAsStringAsync()); - var content = result.Response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); - + var content = result.Response.Content.ReadAsStringAsync().GetAwaiter().GetResult(); + var responseHeaders = GetResponseHeaders(result); return new ResponseContext( result.Response.StatusCode, content, - result.Response.Content.Headers.ToDictionary(x => x.Key.Trim(), x => x.Value), + responseHeaders, result.ElaspedExecution, _loadReponses.ToList() ); + } + /// + /// Gets the response headers from HTTP response from API. + /// + /// The response headers. + /// Dictionary containing API response headers + private Dictionary> GetResponseHeaders(HttpResponseMessageWrapper result) + { + var responseHeaders = result.Response.Headers.ToDictionary(x => x.Key.Trim(), x => x.Value); + foreach (var contentHeader in result.Response.Content.Headers.ToDictionary(x => x.Key.Trim(), x => x.Value)) + { + responseHeaders.Add(contentHeader.Key, contentHeader.Value); + } + return responseHeaders; } /// @@ -313,6 +327,8 @@ public ExecutionContext Debug() { var uri = BuildUri(); + "method".WriteHeader(); + _httpActionContext.HttpAction().ToString().WriteLine(); "host".WriteHeader(); uri.Host.WriteLine(); "absolute path".WriteHeader(); @@ -327,7 +343,6 @@ public ExecutionContext Debug() uri.OriginalString.WriteLine(); "scheme".WriteHeader(); uri.Scheme.WriteLine(); - return this; } } diff --git a/src/RA/ResponseContext.cs b/src/RA/ResponseContext.cs index 183743b..eed538e 100644 --- a/src/RA/ResponseContext.cs +++ b/src/RA/ResponseContext.cs @@ -8,7 +8,8 @@ using RA.Exceptions; using RA.Extensions; using System.Xml.Linq; - +using System.Net.Http; + namespace RA { public class ResponseContext @@ -34,6 +35,16 @@ public ResponseContext(HttpStatusCode statusCode, string content, Dictionary(); Initialize(); + } + + /// + /// Retrieve the specified headerName. + /// + /// Header. + /// Header name. + public string RetrieveHeader(string headerName) + { + return HeaderValue(headerName); } /// @@ -267,7 +278,7 @@ private void Parse() } if (!string.IsNullOrEmpty(_content)) - throw new Exception(string.Format("({0}) not supported", contentType)); + throw new Exception(string.Format("Content type ({0}) not supported", contentType)); } private void ParseLoad() From 1c6e6d125395b32e6f750139bb41f52d01ae327e Mon Sep 17 00:00:00 2001 From: Manish Kumar Date: Wed, 26 Jun 2019 14:57:15 -0400 Subject: [PATCH 2/2] Added the support to send the Form Data. --- README.md | 28 ++++++++++++- src/RA/ExecutionContext.cs | 18 +++++++-- src/RA/FileContent.cs | 4 +- src/RA/FormContent.cs | 18 +++++++++ src/RA/SetupContext.cs | 81 +++++++++++++++++++++++++++++++------- 5 files changed, 128 insertions(+), 21 deletions(-) create mode 100644 src/RA/FormContent.cs diff --git a/README.md b/README.md index e95e4a4..26bb33b 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,16 @@ The call chains are structured around 4 main parts. //Add a file as part of the request. Doing so will convert the body to a multipart/form request. Used for POST, PUT and DELETE //eg: "file name", "file", "image/jpeg", File.ReadAllBytes("pathtofile") - .File("string", "string", "string", byte[]) + .File("name", "string", "string", "string", byte[]) + + // Add the string content to form data as part of the request. It will use the default values of Content-Desposition. + .Form("string", "string content") + + // Add the object content to form data as part of the request. Object will be converted into JSON and added to request. + .Form("string", object) + + // Add the object content to form data as part of the request. Object will be converted into JSON and added to request. You can explicitly define the content type and desposition. + .Form(string name, object content, string contentDispositionName, string contentType) //Set the host of the target server //Useful when you want to reuse a test suite between multiple Uris @@ -305,6 +314,23 @@ new RestAssured() .Debug() ``` +### Using Form +```C# +new RestAssured() + .Given() + .Name("Sending form data") + .Param("id", "some identifier") + //First parameter is variable name of content being sent in form-data. + //Second parameter is the content to be sent. This content will be converted into JSON before being attached to form-data. + //Third parameter describes the Content-Desposition + //Fourth parameter describes the Content-Type + .Form("name", content, "form-data", "multipart/form-data") + .When() + .Post("http://yourremote.com") + .Then() + .Debug() +``` + ### Retrieving an object Value returned from http://yourremote.com ```Javascript diff --git a/src/RA/ExecutionContext.cs b/src/RA/ExecutionContext.cs index 1947d9c..9f90332 100644 --- a/src/RA/ExecutionContext.cs +++ b/src/RA/ExecutionContext.cs @@ -186,7 +186,7 @@ private void SetTimeout() private HttpContent BuildContent() { - if (_setupContext.Files().Any()) + if (_setupContext.Files().Any() || _setupContext.Forms().Any()) return BuildMultipartContent(); if (_setupContext.Params().Any()) return BuildFormContent(); @@ -205,14 +205,24 @@ private HttpContent BuildMultipartContent() _setupContext.Files().ForEach(x => { var fileContent = new ByteArrayContent(x.Content); - fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") + fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(x.ContentDispositionName) { - Name = x.ContentDispositionName.Quote(), + Name = x.Name.Quote(), FileName = x.FileName }; fileContent.Headers.ContentType = new MediaTypeHeaderValue(x.ContentType); - content.Add(fileContent); + }); + + _setupContext.Forms().ForEach(x => + { + var formContent = new StringContent(x.Content); + formContent.Headers.ContentDisposition = new ContentDispositionHeaderValue(x.ContentDispositionName) + { + Name = x.Name.Quote(), + }; + formContent.Headers.ContentType = new MediaTypeHeaderValue(x.ContentType); + content.Add(formContent); }); return content; diff --git a/src/RA/FileContent.cs b/src/RA/FileContent.cs index ef12e6f..181ad73 100644 --- a/src/RA/FileContent.cs +++ b/src/RA/FileContent.cs @@ -2,14 +2,16 @@ namespace RA { public class FileContent { - public FileContent(string fileName, string contentDispositionName, string contentType, byte[] content) + public FileContent(string name, string fileName, string contentDispositionName, string contentType, byte[] content) { + Name = name; FileName = fileName; ContentType = contentType; ContentDispositionName = contentDispositionName; Content = content; } + public string Name { get; private set; } public string FileName { get; private set; } public string ContentType { get; private set; } public string ContentDispositionName { get; private set; } diff --git a/src/RA/FormContent.cs b/src/RA/FormContent.cs new file mode 100644 index 0000000..3bc4f43 --- /dev/null +++ b/src/RA/FormContent.cs @@ -0,0 +1,18 @@ +namespace RA +{ + public class FormContent + { + public FormContent(string name, string content, string contentDispositionName = "form-data", string contentType = "multipart/form-data") + { + Name = name; + Content = content; + ContentType = contentType; + ContentDispositionName = contentDispositionName; + } + + public string Name { get; private set; } + public string Content { get; private set; } + public string ContentType { get; private set; } + public string ContentDispositionName { get; private set; } + } +} \ No newline at end of file diff --git a/src/RA/SetupContext.cs b/src/RA/SetupContext.cs index 82783b5..2265bb6 100644 --- a/src/RA/SetupContext.cs +++ b/src/RA/SetupContext.cs @@ -23,7 +23,8 @@ public class SetupContext private readonly Dictionary _queryStrings = new Dictionary(); private readonly Dictionary _cookies = new Dictionary(); private readonly List _files = new List(); - private TimeSpan? _timeout = null; + private readonly List _forms = new List(); + private TimeSpan? _timeout = null; private Func, List> GetHeaderFor = (filter, headers) => { @@ -177,14 +178,55 @@ public string Body() /// Set a file to be used with a POST/PUT/DELETE action. Adding a file will convert the body /// to a multipart/form. /// + /// Name of multipart data. Generally this name is same as the name of the variable in the bean clas to find the file content to. /// Name of file /// /// eg: image/jpeg or application/octet-stream /// Byte array of the data. File.ReadAllBytes() /// - public SetupContext File(string fileName, string contentDispositionName, string contentType, byte[] content) + public SetupContext File(string name, string fileName, string contentDispositionName, string contentType, byte[] content) { - _files.Add(new FileContent(fileName, contentDispositionName, contentType, content)); + _files.Add(new FileContent(name, fileName, contentDispositionName, contentType, content)); + return this; + } + + /// + /// Set form content to be used with a POST/PUT/DELETE action. + /// + /// Name of form content + /// Form content + /// + public SetupContext Form(string name, string content) + { + _forms.Add(new FormContent(name, content)); + return this; + } + + /// + /// Set form content to be used with a POST/PUT/DELETE action. + /// + /// Name of form content + /// Form content + /// + public SetupContext Form(string name, object content) + { + var contentAsString = JsonConvert.SerializeObject(content); + _forms.Add(new FormContent(name, contentAsString)); + return this; + } + + /// + /// Set form content to be used with a POST/PUT/DELETE action. + /// + /// Name of form content + /// Form content + /// + /// eg: application/json - this may only be needed if you are sending json and files in mutipart-form content + /// + public SetupContext Form(string name, object content, string contentDispositionName, string contentType) + { + var contentAsString = JsonConvert.SerializeObject(content); + _forms.Add(new FormContent(name, contentAsString, contentDispositionName, contentType)); return this; } @@ -194,17 +236,26 @@ public SetupContext File(string fileName, string contentDispositionName, string /// public List Files() { - return _files.Select(x => new FileContent(x.FileName, x.ContentDispositionName, x.ContentType, x.Content)).ToList(); - } - - /// - /// Set a cookie value pair. - /// eg: name : X-XSRF-TOKEN and value : 123456789 - /// - /// - /// - /// - public SetupContext Cookie(string name, string value) + return _files.Select(x => new FileContent(x.Name, x.FileName, x.ContentDispositionName, x.ContentType, x.Content)).ToList(); + } + + /// + /// Returns all forms. + /// + /// + public List Forms() + { + return _forms.Select(x => new FormContent(x.Name, x.Content, x.ContentDispositionName, x.ContentType)).ToList(); + } + + /// + /// Set a cookie value pair. + /// eg: name : X-XSRF-TOKEN and value : 123456789 + /// + /// + /// + /// + public SetupContext Cookie(string name, string value) { if(!_cookies.ContainsKey(name)) _cookies.Add(name, value); @@ -465,7 +516,7 @@ public SetupContext Clone() foreach (var file in _files) { - setupContext.File(file.FileName, file.ContentDispositionName, file.ContentType, file.Content); + setupContext.File(file.Name, file.FileName, file.ContentDispositionName, file.ContentType, file.Content); } return setupContext;