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 d5c97d0..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; @@ -293,16 +303,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 +337,8 @@ public ExecutionContext Debug() { var uri = BuildUri(); + "method".WriteHeader(); + _httpActionContext.HttpAction().ToString().WriteLine(); "host".WriteHeader(); uri.Host.WriteLine(); "absolute path".WriteHeader(); @@ -327,7 +353,6 @@ public ExecutionContext Debug() uri.OriginalString.WriteLine(); "scheme".WriteHeader(); uri.Scheme.WriteLine(); - return this; } } 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/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() 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;