// Copyright (c) Alex Ellis 2017. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestHandler_make(t *testing.T) {
	config := WatchdogConfig{}
	handler := makeRequestHandler(&config)

	if handler == nil {
		t.Fail()
	}
}

func TestHandler_HasCustomHeaderInFunction_WithCgi_Mode(t *testing.T) {
	rr := httptest.NewRecorder()

	body := ""
	req, err := http.NewRequest("POST", "/", bytes.NewBufferString(body))
	req.Header.Add("custom-header", "value")

	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{
		faasProcess: "env",
		cgiHeaders:  true,
	}
	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusOK
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}

	read, _ := ioutil.ReadAll(rr.Body)
	val := string(read)
	if !strings.Contains(val, "Http_ContentLength=0") {
		t.Errorf("'env' should printed: Http_ContentLength=0, got: %s\n", val)
	}
	if !strings.Contains(val, "Http_Custom_Header") {
		t.Errorf("'env' should printed: Http_Custom_Header, got: %s\n", val)
	}

	seconds := rr.Header().Get("X-Duration-Seconds")
	if len(seconds) == 0 {
		t.Errorf("Exec of cat should have given a duration as an X-Duration-Seconds header\n")
	}
}

func TestHandler_HasCustomHeaderInFunction_WithCgiMode_AndBody(t *testing.T) {
	rr := httptest.NewRecorder()

	body := "test"
	req, err := http.NewRequest("POST", "/", bytes.NewBufferString(body))
	req.Header.Add("custom-header", "value")

	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{
		faasProcess: "env",
		cgiHeaders:  true,
	}
	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusOK
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}

	read, _ := ioutil.ReadAll(rr.Body)
	val := string(read)
	if !strings.Contains(val, fmt.Sprintf("Http_ContentLength=%d", len(body))) {
		t.Errorf("'env' should printed: Http_ContentLength=0, got: %s\n", val)
	}
	if !strings.Contains(val, "Http_Custom_Header") {
		t.Errorf("'env' should printed: Http_Custom_Header, got: %s\n", val)
	}

	seconds := rr.Header().Get("X-Duration-Seconds")
	if len(seconds) == 0 {
		t.Errorf("Exec of cat should have given a duration as an X-Duration-Seconds header\n")
	}
}

func TestHandler_DoesntHaveCustomHeaderInFunction_WithoutCgi_Mode(t *testing.T) {
	rr := httptest.NewRecorder()

	body := ""
	req, err := http.NewRequest("POST", "/", bytes.NewBufferString(body))
	req.Header.Add("custom-header", "value")
	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{
		faasProcess: "env",
		cgiHeaders:  false,
	}
	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusOK
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}

	read, _ := ioutil.ReadAll(rr.Body)
	val := string(read)
	if strings.Contains(val, "Http_Custom_Header") {
		t.Errorf("'env' should not have printed: Http_Custom_Header, got: %s\n", val)

	}

	seconds := rr.Header().Get("X-Duration-Seconds")
	if len(seconds) == 0 {
		t.Errorf("Exec of cat should have given a duration as an X-Duration-Seconds header\n")
	}
}

func TestHandler_HasXDurationSecondsHeader(t *testing.T) {
	rr := httptest.NewRecorder()

	body := "hello"
	req, err := http.NewRequest("POST", "/", bytes.NewBufferString(body))
	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{
		faasProcess: "cat",
	}
	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusOK
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}

	seconds := rr.Header().Get("X-Duration-Seconds")
	if len(seconds) == 0 {
		t.Errorf("Exec of cat should have given a duration as an X-Duration-Seconds header\n")
	}
}

func TestHandler_RequestTimeoutFailsForExceededDuration(t *testing.T) {
	rr := httptest.NewRecorder()

	verbs := []string{"POST"}
	for _, verb := range verbs {

		body := "hello"
		req, err := http.NewRequest(verb, "/", bytes.NewBufferString(body))
		if err != nil {
			t.Fatal(err)
		}

		config := WatchdogConfig{
			faasProcess: "sleep 2",
			execTimeout: time.Duration(100) * time.Millisecond,
		}

		handler := makeRequestHandler(&config)
		handler(rr, req)

		required := http.StatusRequestTimeout
		if status := rr.Code; status != required {
			t.Errorf("handler returned wrong status code for verb [%s]: got %v, but wanted %v",
				verb, status, required)
		}
	}
}

func TestHandler_StatusOKAllowed_ForWriteableVerbs(t *testing.T) {
	rr := httptest.NewRecorder()

	verbs := []string{"POST", "PUT", "UPDATE", "DELETE"}
	for _, verb := range verbs {

		body := "hello"
		req, err := http.NewRequest(verb, "/", bytes.NewBufferString(body))
		if err != nil {
			t.Fatal(err)
		}

		config := WatchdogConfig{
			faasProcess: "cat",
		}
		handler := makeRequestHandler(&config)
		handler(rr, req)

		required := http.StatusOK
		if status := rr.Code; status != required {
			t.Errorf("handler returned wrong status code for verb [%s]: got %v, but wanted %v",
				verb, status, required)
		}

		buf, _ := ioutil.ReadAll(rr.Body)
		val := string(buf)
		if val != body {
			t.Errorf("Exec of cat did not return input value, %s", val)
		}
	}
}

func TestHandler_StatusMethodNotAllowed_ForUnknown(t *testing.T) {
	rr := httptest.NewRecorder()

	req, err := http.NewRequest("UNKNOWN", "/", nil)
	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{}
	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusMethodNotAllowed
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}
}

func TestHandler_StatusOKForGETAndNoBody(t *testing.T) {
	rr := httptest.NewRecorder()

	req, err := http.NewRequest("GET", "/", nil)
	if err != nil {
		t.Fatal(err)
	}

	config := WatchdogConfig{
		// writeDebug:  true,
		faasProcess: "date",
	}

	handler := makeRequestHandler(&config)
	handler(rr, req)

	required := http.StatusOK
	if status := rr.Code; status != required {
		t.Errorf("handler returned wrong status code: got %v, but wanted %v",
			status, required)
	}
}
