/*
 * Copyright 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.appengine.tools.mapreduce.impl.handlers;

import static com.google.appengine.tools.mapreduce.MapSettings.CONTROLLER_PATH;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;

import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalMemcacheServiceTestConfig;
import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
import com.google.appengine.tools.development.testing.LocalTaskQueueTestConfig;
import com.google.appengine.tools.mapreduce.MapReduceJob;
import com.google.appengine.tools.mapreduce.MapReduceServlet;

import junit.framework.TestCase;

import org.easymock.EasyMock;

import java.io.PrintWriter;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Tests MapReduceServlet
 *
 */
public class MapReduceServletTest extends TestCase {

  private final LocalServiceTestHelper helper =
      new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig(),
          new LocalTaskQueueTestConfig(), new LocalMemcacheServiceTestConfig());

  private MapReduceServlet servlet;

  @Override
  public void setUp() throws Exception {
    super.setUp();
    helper.setUp();
    servlet = new MapReduceServlet();
  }

  @Override
  public void tearDown() throws Exception {
    helper.tearDown();
    super.tearDown();
  }

  public void testBailsOnBadHandler() throws Exception {
    HttpServletRequest request = createMockRequest("fizzle", true, true);
    HttpServletResponse response = createMock(HttpServletResponse.class);
    replay(request, response);
    try {
      servlet.doPost(request, response);
      fail("Should have thrown RuntimeException");
    } catch (RuntimeException e) {
      // Pass
    }
    verify(request, response);
  }

  public void testCommandError() throws Exception {
    HttpServletRequest request = createMockRequest(
        MapReduceServletImpl.COMMAND_PATH + "/" + StatusHandler.GET_JOB_DETAIL_PATH, false, true);
    expect(request.getMethod()).andReturn("GET").anyTimes();
    HttpServletResponse response = createMock(HttpServletResponse.class);
    PrintWriter responseWriter = createMock(PrintWriter.class);
    responseWriter.write('{');
    responseWriter.write("\"error_class\"");
    responseWriter.write(':');
    responseWriter.write("\"java.lang.RuntimeException\"");
    responseWriter.write(',');
    responseWriter.write("\"error_message\"");
    responseWriter.write(':');
    responseWriter.write("\"Full stack trace is available in the server logs. "
        + "Message: blargh\"");
    responseWriter.write('}');
    responseWriter.flush();
    // This method can't actually throw this exception, but that's not
    // important to the test.
    expect(request.getParameter("mapreduce_id")).andThrow(new RuntimeException("blargh"));
    response.setContentType("application/json");
    expect(response.getWriter()).andReturn(responseWriter).anyTimes();
    replay(request, response, responseWriter);
    servlet.doPost(request, response);
    verify(request, response, responseWriter);
  }

  public void testControllerCSRF() throws Exception {
    // Send it as an AJAX request but not a task queue request - should be denied.
    HttpServletRequest request = createMockRequest(CONTROLLER_PATH, false, true);
    HttpServletResponse response = createMock(HttpServletResponse.class);
    response.sendError(403, "Received unexpected non-task queue request.");
    replay(request, response);
    servlet.doPost(request, response);
    verify(request, response);
  }

  public void testGetJobDetailCSRF() throws Exception {
    // Send it as a task queue request but not an ajax request - should be denied.
    HttpServletRequest request = createMockRequest(
        MapReduceServletImpl.COMMAND_PATH + "/" + StatusHandler.GET_JOB_DETAIL_PATH, true, false);
    expect(request.getMethod()).andReturn("POST").anyTimes();

    HttpServletResponse response = createMock(HttpServletResponse.class);

    // Set before error and last one wins, so this is harmless.
    response.setContentType("application/json");
    EasyMock.expectLastCall().anyTimes();

    response.sendError(403, "Received unexpected non-XMLHttpRequest command.");
    replay(request, response);
    servlet.doGet(request, response);
    verify(request, response);
  }

  public void testStaticResources_jQuery() throws Exception {
    HttpServletResponse resp = createMock(HttpServletResponse.class);
    resp.setContentType("text/javascript");
    resp.setHeader("Cache-Control", "public; max-age=300");
    ServletOutputStream sos = createMock(ServletOutputStream.class);
    expect(resp.getOutputStream()).andReturn(sos);
    sos.write((byte[]) EasyMock.anyObject(), EasyMock.eq(0), EasyMock.anyInt());
    EasyMock.expectLastCall().atLeastOnce();
    sos.flush();
    EasyMock.expectLastCall().anyTimes();
    replay(resp, sos);
    MapReduceServletImpl.handleStaticResources("jquery.js", resp);
    verify(resp, sos);
  }

  public void testStaticResources_status() throws Exception {
    HttpServletResponse resp = createMock(HttpServletResponse.class);
    resp.sendRedirect("/_ah/pipeline/list?class_path=" + MapReduceJob.class.getName());
    replay(resp);
    MapReduceServletImpl.handleStaticResources("status", resp);
    verify(resp);
  }

  private static HttpServletRequest createMockRequest(
      String handler, boolean taskQueueRequest, boolean ajaxRequest) {
    HttpServletRequest request = createMock(HttpServletRequest.class);
    if (taskQueueRequest) {
      expect(request.getHeader("X-AppEngine-QueueName"))
          .andReturn("default")
          .anyTimes();
    } else {
      expect(request.getHeader("X-AppEngine-QueueName"))
          .andReturn(null)
          .anyTimes();
    }
    if (ajaxRequest) {
      expect(request.getHeader("X-Requested-With"))
          .andReturn("XMLHttpRequest")
          .anyTimes();
    } else {
      expect(request.getHeader("X-Requested-With"))
          .andReturn(null)
          .anyTimes();
    }
    expect(request.getPathInfo())
        .andReturn("/" + handler)
        .anyTimes();
    return request;
  }
}
