// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0

package health

import (
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	check "gopkg.in/check.v1"
)

// Gocheck boilerplate
var _ = check.Suite(&Suite{})

func Test(t *testing.T) {
	check.TestingT(t)
}

type Suite struct{}

const (
	goodToken = "supersecret"
	badToken  = "pwn"
)

func (s *Suite) TestPassFailRefuse(c *check.C) {
	h := &Handler{
		Token:  goodToken,
		Prefix: "/_health/",
		Routes: Routes{
			"success": func() error { return nil },
			"miracle": func() error { return errors.New("unimplemented") },
		},
	}

	resp := httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/ping", goodToken))
	s.checkHealthy(c, resp)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/success", goodToken))
	s.checkHealthy(c, resp)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/miracle", goodToken))
	s.checkUnhealthy(c, resp)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/miracle", badToken))
	c.Check(resp.Code, check.Equals, http.StatusForbidden)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/miracle", ""))
	c.Check(resp.Code, check.Equals, http.StatusUnauthorized)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/_health/theperthcountyconspiracy", ""))
	c.Check(resp.Code, check.Equals, http.StatusNotFound)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/x/miracle", ""))
	c.Check(resp.Code, check.Equals, http.StatusNotFound)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/miracle", ""))
	c.Check(resp.Code, check.Equals, http.StatusNotFound)
}

func (s *Suite) TestPingOverride(c *check.C) {
	var ok bool
	h := &Handler{
		Token: goodToken,
		Routes: Routes{
			"ping": func() error {
				ok = !ok
				if ok {
					return nil
				}
				return errors.New("good error")
			},
		},
	}
	resp := httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/ping", goodToken))
	s.checkHealthy(c, resp)

	resp = httptest.NewRecorder()
	h.ServeHTTP(resp, s.request("/ping", goodToken))
	s.checkUnhealthy(c, resp)
}

func (s *Suite) TestZeroValueIsDisabled(c *check.C) {
	resp := httptest.NewRecorder()
	(&Handler{}).ServeHTTP(resp, s.request("/ping", goodToken))
	c.Check(resp.Code, check.Equals, http.StatusNotFound)

	resp = httptest.NewRecorder()
	(&Handler{}).ServeHTTP(resp, s.request("/ping", ""))
	c.Check(resp.Code, check.Equals, http.StatusNotFound)
}

func (s *Suite) request(path, token string) *http.Request {
	u, _ := url.Parse("http://foo.local" + path)
	req := &http.Request{
		Method:     "GET",
		Host:       u.Host,
		URL:        u,
		RequestURI: u.RequestURI(),
	}
	if token != "" {
		req.Header = http.Header{
			"Authorization": {"Bearer " + token},
		}
	}
	return req
}

func (s *Suite) checkHealthy(c *check.C, resp *httptest.ResponseRecorder) {
	c.Check(resp.Code, check.Equals, http.StatusOK)
	c.Check(resp.Body.String(), check.Equals, `{"health":"OK"}`+"\n")
}

func (s *Suite) checkUnhealthy(c *check.C, resp *httptest.ResponseRecorder) {
	c.Check(resp.Code, check.Equals, http.StatusOK)
	var result map[string]interface{}
	err := json.Unmarshal(resp.Body.Bytes(), &result)
	c.Assert(err, check.IsNil)
	c.Check(result["health"], check.Equals, "ERROR")
	c.Check(result["error"].(string), check.Not(check.Equals), "")
}
