package org.mockserver.cors;

import org.junit.Test;
import org.mockserver.configuration.Configuration;
import org.mockserver.model.HttpRequest;
import org.mockserver.model.HttpResponse;

import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.mockserver.configuration.Configuration.configuration;
import static org.mockserver.cors.CORSHeaders.isPreflightRequest;
import static org.mockserver.model.HttpRequest.request;
import static org.mockserver.model.HttpResponse.response;

/**
 * @author jamesdbloom
 */
public class CORSHeadersTest {

    private final Configuration configuration = configuration();

    @Test
    public void shouldDetectPreflightRequest() {
        assertThat(isPreflightRequest(
            configuration,
            request()
                .withMethod("OPTIONS")
                .withHeader("origin", "some_origin_header")
                .withHeader("access-control-request-method", "true")
        ), is(true));
        assertThat(isPreflightRequest(
            configuration,
            request()
                .withMethod("GET")
                .withHeader("origin", "some_origin_header")
                .withHeader("access-control-request-method", "true")
        ), is(false));
        assertThat(isPreflightRequest(
            configuration,
            request()
                .withMethod("OPTIONS")
                .withHeader("not_origin", "some_origin_header")
                .withHeader("access-control-request-method", "true")
        ), is(false));
        assertThat(isPreflightRequest(
            configuration,
            request()
                .withMethod("OPTIONS")
                .withHeader("origin", "some_origin_header")
                .withHeader("not_access-control-request-method", "true")
        ), is(false));
    }

    @Test
    public void shouldAddCORSSpecificDomain() {
        // given
        HttpRequest request = request();
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "www.mock-server.com",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );

        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("www.mock-server.com"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), is("CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-expose-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-max-age"), is("300"));
    }

    @Test
    public void shouldAddCORSHeader() {
        // given
        HttpRequest request = request();
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "*",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );

        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("*"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), is("CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-expose-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-max-age"), is("300"));
    }

    @Test
    public void shouldAddCORSHeaderForNullOrigin() {
        // given
        HttpRequest request = request()
            .withHeader("origin", "null");
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "*",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );


        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("null"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), is("CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-expose-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-max-age"), is("300"));
    }

    @Test
    public void shouldAddCORSHeaderForAllowCredentials() {
        // given
        HttpRequest request = request()
            .withHeader("origin", "some_origin_value");
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "*",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );


        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("some_origin_value"));
        assertThat(response.getFirstHeader("access-control-allow-credentials"), is("true"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), is("CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-expose-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-max-age"), is("300"));
    }

    @Test
    public void shouldAddCORSHeaderForAllowCredentialsWithoutOrigin() {
        // given
        HttpRequest request = request();
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "*",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );


        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("*"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), is("CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-expose-headers"), is("Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization"));
        assertThat(response.getFirstHeader("access-control-max-age"), is("300"));
    }

    @Test
    public void shouldAddCORSHeaderForExtraRequestHeaders() {
        // given
        HttpRequest request = request()
            .withMethod("OPTIONS")
            .withHeader("Access-Control-Request-Headers", "X-API-Key");
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "*",
            "Allow, Content-Encoding, Content-Length, Content-Type, ETag, Expires, Last-Modified, Location, Server, Vary, Authorization",
            "CONNECT, DELETE, GET, HEAD, OPTIONS, POST, PUT, PATCH, TRACE",
            true,
            300
        );


        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-headers"), containsString("X-API-Key"));
    }

    @Test
    public void shouldReflectOriginAndDefaultMethodsHeadersWhenBlank() {
        // given - all CORS settings left blank (the defaults), no credentials, a
        // cross-origin browser request
        HttpRequest request = request()
            .withHeader("origin", "http://localhost:3000");
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "",
            "",
            "",
            false,
            300
        );

        // when
        corsHeaders.addCORSHeaders(request, response);

        // then - origin reflected, and methods/headers default to usable values
        // (not empty) so a PUT preflight from the dashboard passes
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("http://localhost:3000"));
        assertThat(response.getFirstHeader("access-control-allow-methods"), containsString("PUT"));
        assertThat(response.getFirstHeader("access-control-allow-headers"), containsString("Content-Type"));
    }

    @Test
    public void shouldFallBackToAnyOriginWhenAllowOriginBlankAndNoOrigin() {
        // given - default (blank) corsAllowOrigin, no Origin header on the request
        HttpRequest request = request();
        HttpResponse response = response();
        CORSHeaders corsHeaders = new CORSHeaders(
            "",
            "Allow, Content-Type",
            "GET, PUT, OPTIONS",
            false,
            300
        );

        // when
        corsHeaders.addCORSHeaders(request, response);

        // then
        assertThat(response.getFirstHeader("access-control-allow-origin"), is("*"));
    }

}
