I'm attempting to write tests for a very long and kludgy "getPost" method in a webapp I'm working on for my job. I'm using JUnit, HtmlUnit, and Jetty's ServletTester to approximate sending a request to a servlet and receiving a response. I've managed to get it mostly working, but I'm having a problem. I'm trying to test the login functionality. If the user logs in successfully, the server should send some JSON back to the client with the user's information. If the user is already logged in, the server should send back "result": "failure" and an error message.
My problem comes when I try to test the second requirement. I can log in successfully, and get the correct data back. However, when I try to send the request again, it returns 404: not found. I tried breaking the code up into different tests, but I have to be able to call login twice in order to test the second requirement. Later tests in the JUnit file run just fine, and the servlet is staying connected the same time. I tried making a second, identical request, but that also failed. I've searched the internet to no avail. In short, I'm stumped.
Here's what I'm working with (unnecessary code has been edited out):
//In MyFunServlet class:
private final static String USERID_ATTRIBUTENAME = "userid";
void doPost(HttpServletRequest request, HttpServletResponse response) {
String action = request.getParameter("opt");
final HttpSession session = request.getSession();
if(action != null){
Long userId = (Long)session.getAttribute(USERID_ATTRIBUTENAME);
if(userId != null){
//do stuffz
} else {
if(action.equals("login")) {
User user = LoginUser(request, response);
try{
JSONObject json = new JSONObject();
if(request.getAttribute("result") == "success"){
json.put("result", "success");
json.put("id", user.getId());
json.put("name", user.getName());
} else {
json.put("result", "failure");
json.put("message", request.getAttribute("message"));
}
SendJSONResponse(json, request, response);
}catch(Exception e){
}
} else {
System.out.print("Unknown opt: " + action);
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
}
}
private void LoginUser(HttpServletRequest request, HttpServletResponse response){
final HttpSession session = request.getSession();
User user = null;
Long userId = (Long)session.getAttribute(USERID_ATTRIBUTENAME);
if(userId != null){
request.setAttribute("result", "failure");
request.setAttribute("message", "The user is already logged in.");
} else {
final String email = request.getParameter("accountEmail");
final String password = request.getParameter("accountPassword");
if(email != null) {
user = helperClass.magicallyGetUserByEmail(email);
if(user != null){
if(user.getPassword().equals(password)){
session.setAttribute(USERID_ATTRIBUTENAME, user.getId();
request.setAttribute("result", "success");
}
}
} else {
request.setAttribute("result", "failure");
}
}
return user;
}
private void SendJSONResponse(JSONObject json, HttpServletRequest request,
HttpServletResponse response) {
String contentStr = json.toString();
response.setContentType("application/json");
response.setStatus( HttpServletResponse.SC_OK);
response.setContentLength(contentStr.length());
response.getWriter().print(contentStr);
response.flushBuffer();
}
For reference purposes, this file is 1084 lines long. The doPost method is about 900 of those. Disclaimer: this is not my code. I did not write it. I only have to test it.
Now for the test:
//In MyFunServletTest.java:
//using JUnit 4
public class MyFunServletTest {
static ServletTester tester;
static String baseUrl;
WebClient webClient = new WebClient();
User user;
WebRequest loginRequest;
#BeforeClass
public static void initClass(){
tester = new ServletTester;
tester.setContextPath("/");
tester.addServlet(MyFunServlet.class, "/fun.service");
baseUrl = tester.createSocketConnector(true);
tester.start();
}
#AfterClass
public static void cleanClass() {
tester.stop();
}
#Before
public void preTest(){
//taking values from our magical test user
user = new User();
user.setEmail("blah#blah.com");
user.setPassword("secure");
loginRequest = new WebRequest(baseUrl + "/fun.service", HttpMethod.POST);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new NameValuePair("opt","login"));
params.add(new NameValuePair("accountEmail", user.getEmail());
params.add(new NameValuePair("accountPassword", user.getPassword());
loginRequest.setRequestParameters(params);
}
#Test
public void testLogin() {
WebResponse response = webClient.loadWebResponse(loginRequest);
JSONObject responseJSON = new JSONObject(response.getContentAsString());
//this test passes
assertEquals("success", responseJSON.getString("result"));
response = webClient.loadWebResponse(loginRequest);
//this test fails
assertTrue(404 != response.getStatusCode());
//this then causes an error, as response.getContentAsString() is null.
esponseJSON = new JSONObject(response.getContentAsString());
}
}
Help? Where am I missing something?
Thanks.
Without the ability to run the test myself, I can only offer some approaches:
Try creating two JSONObject objects to store the two responses separately, and compare the two (either print them or using the debugger), see if anything looks odd there.
If that doesn't tell you anything, create two separate identical request instances and use each.
Then try tracing through the call to loadWebResponse to see exactly what URL is being requested (cranking up the log level might tell you this, too).
If the 404 is correct, then the second request is somehow being mangled, but the question would be WHERE.
Related
I am trying to make a log in system using spring. Problem is if username is not in the database I want to send a different status code and if username is in the database but password is wrong I want to send different status code. Because in my front end i am going to inform user using different alerts according to status code.
I cannot use HttpStatus.NOT_ACCEPTABLE or something like that because my controller is returning a User(my custom class). It will either return User or null.
#GetMapping("/users")
public User userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return null;
}
if(user.getPassword().equals(password)) {
return user;
} else {
return null;
}
}
Here I am trying to change status while returning nulls.
you can return ResponseEntity to meet your requirement
#GetMapping("/users")
public ResponseEntity<User> userLogin(#RequestParam String username,#RequestParam String password) {
User user = userService.findByUsername(username);
if(user==null) {
return new ResponseEntity<>(null,HttpStatus.NOT_FOUND);
}
if(user.getPassword().equals(password)) {
return new ResponseEntity<>(user,HttpStatus.OK);
} else {
return new ResponseEntity<>(null,HttpStatus.FORBIDDEN);
}
}
Spring 5 introduced the ResponseStatusException class. We can create an instance of it providing an HttpStatus and optionally a reason and a cause:
#GetMapping(value = "/{id}") public Foo findById(#PathVariable("id") Long id, HttpServletResponse response) {
try {
Foo resourceById = RestPreconditions.checkFound(service.findOne(id));
eventPublisher.publishEvent(new SingleResourceRetrievedEvent(this, response));
return resourceById;
}
catch (MyResourceNotFoundException exc) {
throw new ResponseStatusException(
HttpStatus.NOT_FOUND, "Foo Not Found", exc);
} }
Maybe this is which you looking for?
Detail in https://www.baeldung.com/exception-handling-for-rest-with-spring#controlleradvice
I have the following service subscription in my component:
this.userService.loginUser(this.user).subscribe(users => {
if (users == null) {
console.log('users is null');
this.isValid = !this.isValid;
} else {
this.userService.subscribers.next(users);
localStorage.setItem('user', JSON.stringify(users));
console.log(`User, ${this.user.username}, successfully logged in!`);
console.log(localStorage.getItem('user'));
this.router.navigate(['dashboard']);
}
});
The service method looks like this:
public loginUser(user: User) {
console.log(`Attempting to login user: ${user.username}`);
const json = JSON.stringify(user);
this.currentUser = user;
return this.http.post<User>(API_URL + 'login', json, HTTP_OPTIONS);
}
The post request goes to this java servlet method:
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("login attempt");
if (req.getInputStream() != null) {
ObjectMapper mapper = new ObjectMapper();
User user = mapper.readValue(req.getInputStream(), User.class);
user = userService.loginUser(user);
if (user != null) {
user.setPassword("");
HttpSession session = req.getSession();
session.setAttribute("user", user);
}
}
}
On the java end, a username and password match is found and the session attribute is correctly set. However, on the Angular end, 'users' is null, as evidenced by the console.log('users is null') in the first code block. So my question is, why is this null? Can someone please help me understand the mechanics of this? I'm having a very hard time grasping how certain aspects of Angular even work...
So the problem was that in the servlet I didn't have a response. At the end I needed
PrintWriter out = resp.getWriter();
out.write(mapper.writeValueAsString(user));
I'm writing an android application which uses rest services for user regitration and more but running into trouble with my login service. for some reason the requestparams i put into my service call on android side are not being found within my rest service.
could anny 1 tell me what i'm doing wrong or link to a guide which explains how to solve this problem?
Relevant android functions:
public void loginUser(View view) {
// Get username and password values
String username = usernameEdit.getText().toString();
String password = passwordEdit.getText().toString();
// Instantiate Http Request Param Object
RequestParams params = new RequestParams();
// Check if username & password is not null
if(Utility.isNotNull(username) && Utility.isNotNull(password)) {
// Http parameters
params.put("username", username);
params.put("password", password);
invokeWS(params);
} else {
Toast.makeText(getApplicationContext(), "Vul een gebruikersnaam en of " +
"wachtwoord in", Toast.LENGTH_LONG).show();
}
}
// Method that performs RESTful webservice invocations
public void invokeWS(RequestParams params) {
// Make RESTful webservice call using AsyncHttpClient object
AsyncHttpClient client = new AsyncHttpClient();
client.post("http://10.0.2.2:8080/NTR_application/rest/session", params, new AsyncHttpResponseHandler() {
// When the response returned by REST has Http response code '200'
#Override
public void onSuccess(String response) {
Toast.makeText(getApplicationContext(), "You are successfully logged in!" + response, Toast.LENGTH_LONG).show();
// Gets an JSON object with user Data
// Write user Data to SQLite
User user = new Gson().fromJson(response, User.class);
db.addUser(user);
// Navigate to Home screen
navigatetoHomeActivity();
}
// When the response returned by REST has Http response code other than '200'
#Override
public void onFailure(int statusCode, Throwable error,
String content) {
Toast.makeText(getApplicationContext(), "ERROR!" + content + error + statusCode, Toast.LENGTH_LONG).show();
}
});
}
and the rest services which is called :
#Path("/session")
public class UserService {
private Controller controller = new Controller();
#POST //Post so you can't see the information in the browser history easily
#Produces(MediaType.APPLICATION_JSON)
public Response authenticate(#QueryParam("username") String username, #QueryParam("password") String password){
User user = null;
try {
user = controller.authenticate(username, password);
} catch (NoSuchAlgorithmException | SQLException e) {
System.out.println("Authentication caught an exception; failed for: " + username);
e.printStackTrace();
}
if (user != null){
String json = new Gson().toJson(user);
return Response.status(200).entity(json).build();
} else {
return Response.status(401).entity("Username and/or password is incorrect").build();
}
}
}
Mistake was obvious once i saw it, since i use a #POST i need to use #FormParam instead of #QueryParam.
tutorial i used to write these methods used #GET to login which is insecure.
I am successfully using Google Cloud Endpoints. Now for custom user auth, I want to use HTTPSession. The problem is, the initial session is not being reused in future calls, and new session are created (I can see from datastore admin that the session all exists, _AH_SESSION entity). As instructed in the docs, i have enabled it in appengine-web.xml:
<sessions-enabled>true</sessions-enabled>
I made some sample code to narrow it down:
#Api(name = "loginService", version = "v0.1.5")
public class LoginService {
private static final Logger log = Logger.getLogger(LoginService.class.getName());
#ApiMethod(name = "login", path= "login")
public LoginResponse login(HttpServletRequest req)
{
HttpSession session = req.getSession(false);
if(session == null){
log.warning("creating new session");
session = req.getSession(true);
}
LoginResponse resp = new LoginResponse();
resp.statusCode = 200;
resp.statusMessage = "SessionId:" + session.getId();
return resp;
}
#ApiMethod(name = "show", path= "show")
public LoginResponse show(HttpServletRequest req)
{
//session should exist when calling LOGIN first
HttpSession session = req.getSession(false); //NULLPOINTER since session from login is not being reused/found!
LoginResponse resp = new LoginResponse();
resp.statusCode = 200;
resp.statusMessage = "SessionId:" + session.getId();
return resp;
}
public class LoginResponse implements Serializable{
public int statusCode;
public String statusMessage;
}
}`
So first, I call the login method, this creates a new session and prints me the session id. Then in the next request (both using Postman - which should track sessions - in Chrome as the API explorer) i call the 'show' endpoint, and there the previous session does not exist anymore, hence the nullpointer exception.
In the comments on this post, user mikO says endpoints don't keep the session. Is this the reason? I don't really understand the reason behind it. When I just deploy a 'regular' servlet on appengine, it DOES work using Postman or just browsing.. (testing by calling the getter twice, i see that the previous session is being picked up), so it seems that the comment in that post could be right, but i really don't understand why. Working code without endpoints:
public class LoginServlet extends HttpServlet {
private static final Logger log = Logger.getLogger(LoginServlet.class.getName());
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HttpSession s = request.getSession(false);
if (s == null) {
log.warning("creating new session");
s = request.getSession(true);
}
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>" + s.getId() + "</h1>");
}
}
Thanks!
I am trying to write a server side Facebook Notification service in my GWT app. The idea is that I will run this as a timertask or cron job sort of.
With the code below, I get a login URL, I want to be able to Login programmatically as this is intended to be automated (Headless sort of way). I was gonna try do a submit with HTMLunit but I thought the FB API should cater for this.
Please advice.
public class NotificationServiceImpl extends RemoteServiceServlet implements NotificationService {
/**serialVersionUID*/
private static final long serialVersionUID = 6893572879522128833L;
private static final String FACEBOOK_USER_CLIENT = "facebook.user.client";
long facebookUserID;
public String sendMessage(Notification notification) throws IOException {
String api_key = notification.getApi_key();
String secret = notification.getSecret_key();
try {
// MDC.put(ipAddress, req.getRemoteAddr());
HttpServletRequest request = getThreadLocalRequest();
HttpServletResponse response = getThreadLocalResponse();
HttpSession session = getThreadLocalRequest().getSession(true);
// session.setAttribute("api_key", api_key);
IFacebookRestClient<Document> userClient = getUserClient(session);
if(userClient == null) {
System.out.println("User session doesn't have a Facebook API client setup yet. Creating one and storing it in the user's session.");
userClient = new FacebookXmlRestClient(api_key, secret);
session.setAttribute(FACEBOOK_USER_CLIENT, userClient);
}
System.out.println("Creating a FacebookWebappHelper, which copies fb_ request param data into the userClient");
FacebookWebappHelper<Document> facebook = new FacebookWebappHelper<Document>(request, response, api_key, secret, userClient);
String nextPage = request.getRequestURI();
nextPage = nextPage.substring(nextPage.indexOf("/", 1) + 1); //cut out the first /, the context path and the 2nd /
System.out.println(nextPage);
boolean redirectOccurred = facebook.requireLogin(nextPage);
if(redirectOccurred) {
return null;
}
redirectOccurred = facebook.requireFrame(nextPage);
if(redirectOccurred) {
return null;
}
try {
facebookUserID = userClient.users_getLoggedInUser();
if (userClient.users_hasAppPermission(Permission.STATUS_UPDATE)) {
userClient.users_setStatus("Im testing Facebook With Java! This status is written using my Java code! Can you see it? Cool :D", false);
}
} catch(FacebookException ex) {
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Error while fetching user's facebook ID");
System.out.println("Error while getting cached (supplied by request params) value " +
"of the user's facebook ID or while fetching it from the Facebook service " +
"if the cached value was not present for some reason. Cached value = {}" + userClient.getCacheUserId());
return null;
}
// MDC.put(facebookUserId, String.valueOf(facebookUserID));
// chain.doFilter(request, response);
} finally {
// MDC.remove(ipAddress);
// MDC.remove(facebookUserId);
}
return String.valueOf(facebookUserID);
}
public static FacebookXmlRestClient getUserClient(HttpSession session) {
return (FacebookXmlRestClient)session.getAttribute(FACEBOOK_USER_CLIENT);
}
}
Error message:
[ERROR] com.google.gwt.user.client.rpc.InvocationException: <script type="text/javascript">
[ERROR] top.location.href = "http://www.facebook.com/login.php?v=1.0&api_key=MY_KEY&next=notification";
[ERROR] </script>