• No results found

public void login() {

Response response = httpMessageSender.getResponseFrom("/login"); String authenticity_token =

getAuthenticityTokenFromResponse(response); // post the login form and get the cookies

response = loginUserPost(username, password, authenticity_token); if(response.getStatusCode()==302) {

//get the cookies

loggedInCookieJar = response.getCookies(); }else{

System.out.println(response.asString()); new RuntimeException("Could not login"); }

}

Above code can be summarised as:

Use the HttpMessageSender method to issue a GET request on the /login url.

Parse the response to get the authenticity token.

Post a form message using the authenticity token.

Capture the cookies from the response as loggedInCookieJar to use in future

messages. And in more detail.

About

HttpMessageSender

The HttpMessageSender GET request issues a GET on the /login url: Response response = httpMessageSender.getResponseFrom("/login");

This is implemented using REST Assured in the HttpMessageSender as follows: public Response getResponseFrom(String endpoint){

return getResponseFrom(endpoint, anEmptyCookieJar() ); }

public Response getResponseFrom(String endpoint,

Map<String, String> cookieJar) { URL theEndPointUrl = createEndPointURL(url, endpoint);

String ct = "text/html,application/xhtml+xml," + "application/xml;q=0.9,*/*;q=0.8";

Response ret = given().

basic(authUser, authPassword). cookies(cookieJar).

contentType(ct).

get(theEndPointUrl.toExternalForm()). andReturn();

return setLastResponse(ret); }

In one respect I’m just using RestAssured as a simple and easy to use HTTP library.

But really, I’m adding abstraction layers to the code to make each class readable and maintainable for the domain that it targets.

HttpMessageSender works at the HTTP domain and sends HTTP messages.

But… this isn’t completely true since it is HTTP messages specifically for the Tracks application.

Hence the reason why you can see the contentType has been configured to xml, because the Tracks API uses XML.

Other points to note:

I pass in the cookie jar that I’m building up across different messages. And this is just a simple Map.

I don’t use any of the REST Assured assertions because I’m using REST Assured to construct and send HTTP messages.

I store the response as lastResponse so that I can retrieve it if I need to.

The method returns a Response which is a RestAssured object. This means that

I haven’t isolated REST Assured to just the HTTP messages, I also use it in the

TrackAppAsApi and TracksApi.

The Basic Auth message header is configured in the messages with

auth().preemptive().basic(authUser, authPassword)

preemptive means that only one request is sent to the server, if you take

this out then you’ll see multiple messages are sent when a request is made. You can find out more about the pre-emptive and challenge modes for Basic

Authentication on the REST Assured website.

github.com/rest-assured/rest-assured/wiki/usage#preemptive-basic- authentication

Now that we know how the message is sent, we have to examine the response and extract the authenticity token.

If I look at an extract of the contents of the response body:

response.body().asString()

The login form in the response looks a bit like this:

<h3>Please log in to use Tracks:</h3>

<div id="database_auth_form" style="display:block">

<form accept-charset="UTF-8" action="/login" method="post"> <div style="display:none">

<input name="utf8" type="hidden" value="&#x2713;" /> <input name="authenticity_token" type="hidden"

value="GVnfu2oUd6HvhXf2bEJSfl7U2M0n/TwKZu925E97nuQ=" /> </div> <table> <tr> <td> <label for="user_login">Login:</label> </td> <td>

<input type="text" name="user_login" id="user_login" value="" class="login_text" />

</td> </tr> ...

And you can see the authenticity_token is present as a hidden field.

We want to extract that value so that we can pass it in with our HTTP message.

I extract it using the XmlPath functionality from REST Assured. Which requires me

to use Groovy’s GPath syntax for a query.

private String getAuthenticityTokenFromResponse(

Response response){ // get the authenticity_token from the response

XmlPath htmlParser = response.body().htmlPath(); String auth_token_path =

"**.find {it.@name =='authenticity_token'}.@value"; String authenticity_token = htmlParser.get(auth_token_path); return authenticity_token;

}

This query basically says:

**.find - search all nodes for…

{it.@name =='authenticity_token'} - elements with a name attribute with

.@value - and return the value attribute

I found this syntax quite hard to use initially and it took me some time to find helpful documentation.

Eventually I used a combination of the REST Assured documentation, GPath documentation and StackOverflow to build the query.

github.com/rest-assured/rest-assured/wiki/usage#xml-using-xmlpath blog.jayway.com/2013/04/12/whats-new-in-rest-assured-1-8/ hascode.com/2011/10/ testing-restful-web-services-made-easy-using-the-rest-assured-framework stackoverflow.com/questions/2117739/ what-is-the-full-syntax-of-groovys-gpath-expressions

Sending the

login

Form

response = loginUserPost(username, password, authenticity_token);

The TracksAppAsApi class login method sends the login form by calling the

following private method called loginUserPost. This method uses the

authenticity_token that we extracted from the page returned in the previous HTTP

request.

private Response loginUserPost(String username, String password, String authenticityToken) {

UrlParams params = new UrlParams(); params.add("utf8","%E2%9C%93");

params.addEncoded("authenticity_token", authenticityToken); params.add("user_login", username); params.add("user_password", password); params.add("user_noexpiry", "on"); params.add("login", "Sign+in");

Response response = httpMessageSender.postFormMessageTo(

params.toString(), "/login");

return response; }

You can see that I’m using a UrlParams class that I created to make it easier to build

up a String of parameters without having to encode them or add “&” between all the parameters.

This calls the HttpMessageSender method postFormMessageTo to post the form to

the '/login' end point.

The postFormMessageTo methods call the postMessageTo method but set the

contentType header to a form submission "application/x-www-form-urlencoded" public Response postFormMessageTo(String msg, String endpoint){

return postFormMessageTo(msg, endpoint, anEmptyCookieJar()); }

public Response postFormMessageTo(String msg, String endpoint, Map<String, String> cookieJar){ return postMessageTo(msg, endpoint,

"application/x-www-form-urlencoded", cookieJar); }

And the postMessageTo method sends the actual HTTP message: private Response postMessageTo(String msg, String endpoint, String contentType,

Map<String, String> cookieJar){ URL theEndPointUrl = createEndPointURL(url, endpoint); Response ret =

given().

auth().preemptive().

basic(authUser, authPassword). body(msg).

contentType(contentType). cookies(cookieJar).

when().

post(theEndPointUrl.toExternalForm()). andReturn();

// ignore CREATED UNAUTHORIZED CONFLICT

if( ret.statusCode()!=201 && ret.statusCode()!=401 && ret.statusCode()!=409 ){

System.out.println("POTENTIAL BUG - " +

ret.statusCode() + " FOR " + endpoint + "\n" + msg ); }

return setLastResponse(ret); }

Using RestAssured as an HTTP library. preemptive Basic Authentication is used.

A cookie jar is used to maintain a session across HTTP messages.

I have coded a warning to alert me for the presence of any potential bugs or unexpected response codes, to support my monitoring and testing.

Related documents