<commit_before>// Copyright 2016 ePoxy Authors
//
// 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 handler provides functions for responding to specific client
// requests by the ePoxy boot server.
package handler

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/m-lab/epoxy/storage"
	"github.com/m-lab/epoxy/template"
	"src/github.com/m-lab/epoxy/extension"
)

// Config provides access to Host records.
type Config interface {
	Save(host *storage.Host) error
	Load(name string) (*storage.Host, error)
}

// Env holds data necessary for executing handler functions.
type Env struct {
	// Config provides access to Host records.
	Config Config
	// ServerAddr is the host:port of the public service. Used to generate absolute URLs.
	ServerAddr string
}

// GenerateStage1IPXE creates the stage1 iPXE script for booting machines.
func (env *Env) GenerateStage1IPXE(rw http.ResponseWriter, req *http.Request) {
	hostname := mux.Vars(req)["hostname"]

	// Use hostname as key to load record from Datastore.
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}
	// TODO(soltesz):
	// * Verify that the source IP maches the host IP.
	// * Save information sent in PostForm.

	// Generate new session IDs.
	host.GenerateSessionIDs()

	// Save host record to Datastore to commit session IDs.
	if err := env.Config.Save(host); err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	// Generate iPXE script.
	script := template.FormatStage1IPXEScript(host, env.ServerAddr)

	// Complete request as successful.
	rw.Header().Set("Content-Type", "text/plain; charset=us-ascii")
	rw.WriteHeader(http.StatusOK)
	_, err = fmt.Fprintf(rw, script)
	if err != nil {
		log.Printf("Failed to write response to %q: %v", hostname, err)
	}
	return
}

// GenerateJSONConfig creates and returns a JSON serialized nextboot.Config
// suitable for responding to stage2 or stage3 requests.
func (env *Env) GenerateJSONConfig(rw http.ResponseWriter, req *http.Request) {
	hostname := mux.Vars(req)["hostname"]
	// TODO: Verify that the sessionID matches the host.CurrentSessionIDs.Stage2ID.
	// sessionID := mux.Vars(req)["sessionID"]

	// Use hostname as key to load record from Datastore.
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}
	// TODO(soltesz):
	// * Verify that the source IP maches the host IP.
	// * Save information sent in PostForm, e.g. ssh host key.
	stage := path.Base(req.URL.Path)

	script := template.FormatJSONConfig(host, stage)

	// Complete request as successful.
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.WriteHeader(http.StatusOK)
	_, err = fmt.Fprintf(rw, script)
	if err != nil {
		log.Printf("Failed to write response to %q: %v", hostname, err)
	}
	return
}

// ReceiveReport handles the last step of a boot sequence when the epoxy client reports
// success or failure. In both cases, the session ids are invalidated. In all cases,
// epoxy_client is expected to report the server's public host key.
func (env *Env) ReceiveReport(rw http.ResponseWriter, req *http.Request) {
	// TODO: Verify that the source IP maches the host IP.
	// TODO: log or save values where appropriate.
	req.ParseForm()

	// Use hostname as key to load record from Datastore.
	hostname := mux.Vars(req)["hostname"]
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}

	// Verify sessionID matches the host record (i.e. request is authorized).
	sessionID := mux.Vars(req)["sessionID"]
	if sessionID != host.CurrentSessionIDs.ReportID {
		http.Error(rw, "Given session ID does not match host record", http.StatusForbidden)
		return
	}

	host.LastReport = time.Now()
	status := req.PostForm.Get("message")
	if status == "success" {
		// When the status is success, disable the "update" and mark the time.
		host.LastSuccess = host.LastReport
		host.UpdateEnabled = false
		// TODO: invalidate session ids.
	}

	// Save the new host state.
	if err := env.Config.Save(host); err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	// TODO: log using structured JSON.
	log.Println(req.PostForm)

	// Report success with no content.
	rw.WriteHeader(http.StatusNoContent)
	return
}

// newReverseProxy creates an httputil.ReverseProxy instance with a custom Director
// that will send the given content to the target.
//
// This implementation differs from the builtin httputil.NewSingleHostReverseProxy by
// modifying how the target URL is treated and by completely overwriting the request
// body. In this way we are restricting how the request is delivered to the
// target and leveraging the response forwarding logic of the httputil.ReverseProxy.
func newReverseProxy(target *url.URL, content string) *httputil.ReverseProxy {
	director := func(req *http.Request) {
		// Overwrite request URL with target, which discards any client query parameters.
		// Overwrite the client request body with the given content.
		// Overwrite the ContentLength to match the given content.
		// Everything else (e.g. original Headers) is unchanged.
		req.URL = target
		req.Body = ioutil.NopCloser(strings.NewReader(content))
		req.ContentLength = int64(len(content))
	}
	return &httputil.ReverseProxy{Director: director}
}

// HandleExtension handles client requests to ePoxy extension URLs. The handler creates
// and sends a webhook request to the extension service registered for the operation.
func (env *Env) HandleExtension(rw http.ResponseWriter, req *http.Request) {
	// Use hostname as key to load record from Datastore.
	hostname := mux.Vars(req)["hostname"]
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}

	// Verify sessionID matches the host record (i.e. request is authorized).
	sessionID := mux.Vars(req)["sessionID"]
	// TODO: support multiple extensions.
	if sessionID != host.CurrentSessionIDs.ExtensionID {
		http.Error(rw, "Given session ID does not match host record", http.StatusForbidden)
		return
	}

	operation := mux.Vars(req)["operation"]
	if len(operation) == 0 {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}
	// TODO: support multiple operations.
	if operation != "k8stoken" {
		http.Error(rw, "Only the k8stoken operation is supported", http.StatusBadRequest)
		return
	}
	// TODO: load extension URL from datastore.
	if _, ok := storage.Extensions[operation]; !ok {
		http.Error(rw, "Unknown Extension for operation: "+operation, http.StatusInternalServerError)
		return
	}

	webreq := extension.WebhookRequest{
		V1: &extension.V1{
			Hostname:    host.Name,
			IPv4Address: host.IPv4Addr,
			LastBoot:    host.LastSessionCreation,
		},
	}

	extURL, err := url.Parse(storage.Extensions[operation])
	if err != nil {
		http.Error(rw, "Failed to parse extension URL for operation: "+operation, http.StatusInternalServerError)
		return
	}

	// TODO: track metrics about extension requests:
	//  * histogram of request latencies,
	//  * counts status codes,
	srv := newReverseProxy(extURL, webreq.Encode())
	srv.ServeHTTP(rw, req)
}
<commit_msg>Fix import path<commit_after>// Copyright 2016 ePoxy Authors
//
// 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 handler provides functions for responding to specific client
// requests by the ePoxy boot server.
package handler

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httputil"
	"net/url"
	"path"
	"strings"
	"time"

	"github.com/gorilla/mux"
	"github.com/m-lab/epoxy/extension"
	"github.com/m-lab/epoxy/storage"
	"github.com/m-lab/epoxy/template"
)

// Config provides access to Host records.
type Config interface {
	Save(host *storage.Host) error
	Load(name string) (*storage.Host, error)
}

// Env holds data necessary for executing handler functions.
type Env struct {
	// Config provides access to Host records.
	Config Config
	// ServerAddr is the host:port of the public service. Used to generate absolute URLs.
	ServerAddr string
}

// GenerateStage1IPXE creates the stage1 iPXE script for booting machines.
func (env *Env) GenerateStage1IPXE(rw http.ResponseWriter, req *http.Request) {
	hostname := mux.Vars(req)["hostname"]

	// Use hostname as key to load record from Datastore.
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}
	// TODO(soltesz):
	// * Verify that the source IP maches the host IP.
	// * Save information sent in PostForm.

	// Generate new session IDs.
	host.GenerateSessionIDs()

	// Save host record to Datastore to commit session IDs.
	if err := env.Config.Save(host); err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	// Generate iPXE script.
	script := template.FormatStage1IPXEScript(host, env.ServerAddr)

	// Complete request as successful.
	rw.Header().Set("Content-Type", "text/plain; charset=us-ascii")
	rw.WriteHeader(http.StatusOK)
	_, err = fmt.Fprintf(rw, script)
	if err != nil {
		log.Printf("Failed to write response to %q: %v", hostname, err)
	}
	return
}

// GenerateJSONConfig creates and returns a JSON serialized nextboot.Config
// suitable for responding to stage2 or stage3 requests.
func (env *Env) GenerateJSONConfig(rw http.ResponseWriter, req *http.Request) {
	hostname := mux.Vars(req)["hostname"]
	// TODO: Verify that the sessionID matches the host.CurrentSessionIDs.Stage2ID.
	// sessionID := mux.Vars(req)["sessionID"]

	// Use hostname as key to load record from Datastore.
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}
	// TODO(soltesz):
	// * Verify that the source IP maches the host IP.
	// * Save information sent in PostForm, e.g. ssh host key.
	stage := path.Base(req.URL.Path)

	script := template.FormatJSONConfig(host, stage)

	// Complete request as successful.
	rw.Header().Set("Content-Type", "application/json; charset=utf-8")
	rw.WriteHeader(http.StatusOK)
	_, err = fmt.Fprintf(rw, script)
	if err != nil {
		log.Printf("Failed to write response to %q: %v", hostname, err)
	}
	return
}

// ReceiveReport handles the last step of a boot sequence when the epoxy client reports
// success or failure. In both cases, the session ids are invalidated. In all cases,
// epoxy_client is expected to report the server's public host key.
func (env *Env) ReceiveReport(rw http.ResponseWriter, req *http.Request) {
	// TODO: Verify that the source IP maches the host IP.
	// TODO: log or save values where appropriate.
	req.ParseForm()

	// Use hostname as key to load record from Datastore.
	hostname := mux.Vars(req)["hostname"]
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}

	// Verify sessionID matches the host record (i.e. request is authorized).
	sessionID := mux.Vars(req)["sessionID"]
	if sessionID != host.CurrentSessionIDs.ReportID {
		http.Error(rw, "Given session ID does not match host record", http.StatusForbidden)
		return
	}

	host.LastReport = time.Now()
	status := req.PostForm.Get("message")
	if status == "success" {
		// When the status is success, disable the "update" and mark the time.
		host.LastSuccess = host.LastReport
		host.UpdateEnabled = false
		// TODO: invalidate session ids.
	}

	// Save the new host state.
	if err := env.Config.Save(host); err != nil {
		http.Error(rw, err.Error(), http.StatusInternalServerError)
		return
	}

	// TODO: log using structured JSON.
	log.Println(req.PostForm)

	// Report success with no content.
	rw.WriteHeader(http.StatusNoContent)
	return
}

// newReverseProxy creates an httputil.ReverseProxy instance with a custom Director
// that will send the given content to the target.
//
// This implementation differs from the builtin httputil.NewSingleHostReverseProxy by
// modifying how the target URL is treated and by completely overwriting the request
// body. In this way we are restricting how the request is delivered to the
// target and leveraging the response forwarding logic of the httputil.ReverseProxy.
func newReverseProxy(target *url.URL, content string) *httputil.ReverseProxy {
	director := func(req *http.Request) {
		// Overwrite request URL with target, which discards any client query parameters.
		// Overwrite the client request body with the given content.
		// Overwrite the ContentLength to match the given content.
		// Everything else (e.g. original Headers) is unchanged.
		req.URL = target
		req.Body = ioutil.NopCloser(strings.NewReader(content))
		req.ContentLength = int64(len(content))
	}
	return &httputil.ReverseProxy{Director: director}
}

// HandleExtension handles client requests to ePoxy extension URLs. The handler creates
// and sends a webhook request to the extension service registered for the operation.
func (env *Env) HandleExtension(rw http.ResponseWriter, req *http.Request) {
	// Use hostname as key to load record from Datastore.
	hostname := mux.Vars(req)["hostname"]
	host, err := env.Config.Load(hostname)
	if err != nil {
		http.Error(rw, err.Error(), http.StatusNotFound)
		return
	}

	// Verify sessionID matches the host record (i.e. request is authorized).
	sessionID := mux.Vars(req)["sessionID"]
	// TODO: support multiple extensions.
	if sessionID != host.CurrentSessionIDs.ExtensionID {
		http.Error(rw, "Given session ID does not match host record", http.StatusForbidden)
		return
	}

	operation := mux.Vars(req)["operation"]
	if len(operation) == 0 {
		http.Error(rw, err.Error(), http.StatusBadRequest)
		return
	}
	// TODO: support multiple operations.
	if operation != "k8stoken" {
		http.Error(rw, "Only the k8stoken operation is supported", http.StatusBadRequest)
		return
	}
	// TODO: load extension URL from datastore.
	if _, ok := storage.Extensions[operation]; !ok {
		http.Error(rw, "Unknown Extension for operation: "+operation, http.StatusInternalServerError)
		return
	}

	webreq := extension.WebhookRequest{
		V1: &extension.V1{
			Hostname:    host.Name,
			IPv4Address: host.IPv4Addr,
			LastBoot:    host.LastSessionCreation,
		},
	}

	extURL, err := url.Parse(storage.Extensions[operation])
	if err != nil {
		http.Error(rw, "Failed to parse extension URL for operation: "+operation, http.StatusInternalServerError)
		return
	}

	// TODO: track metrics about extension requests:
	//  * histogram of request latencies,
	//  * counts status codes,
	srv := newReverseProxy(extURL, webreq.Encode())
	srv.ServeHTTP(rw, req)
}
<|endoftext|>


<commit_before>package handlers

import (
	"fmt"
	"net/http"
    "net/url"
    "io/ioutil"
    "strings"
    "github.com/nelsonleduc/calmanbot/handlers/models"
    "encoding/json"
    "bytes"
    "regexp"
    "sort"
)

func isValidHTTPURLString(s string)  bool {
    URL, _ := url.Parse(s)
    return (URL.Scheme == "http" || URL.Scheme == "https")
}

func HandleCalman(w http.ResponseWriter, r *http.Request) {

    message := ParseMessageJSON(r.Body)
    bot, _ := models.FetchBot(message.GroupID)

    if !strings.HasPrefix(strings.ToLower(message.Text[1:]), strings.ToLower(bot.BotName)) {
        return
    }

    actions, _ := models.FetchActions(true)
    sort.Sort(models.ByPriority(actions))

    var (
        act models.Action
        sMatch string
    )
    for _, a := range actions {
        r, _ := regexp.Compile("(?i)" + *a.Pattern)
        matched := r.FindStringSubmatch(message.Text)
        if len(matched) > 1 && matched[1] != "" {
            sMatch = matched[1]
            act = a
            break
        }
    }

    postString := ""
    for {
        updateAction(&act, sMatch)
        if act.IsURLType() {
            postString = handleURLAction(act, w, bot)
        } else {
            postString = act.Content
        }

        if postString != "" || act.FallbackAction == nil {
            break
        } else {
            act, _ = models.FetchAction(*act.FallbackAction)
        }
    }

    if postString != "" {
        fmt.Printf("Action: %v\n", act.Content)
        fmt.Printf("Posting: %v\n", postString)
        postText(bot, postString)
    }
}

func handleURLAction(a models.Action, w http.ResponseWriter, b models.Bot) string {

    fmt.Fprintln(w, a)
    resp, err := http.Get(a.Content)

    if err == nil {

        content, _ := ioutil.ReadAll(resp.Body)
        pathString := *a.DataPath

        str := ParseJSON(content, pathString)
        if str == "" {
            return ""
        } else {

            if !validateURL(str) {
                fmt.Printf("Invalid URL: %v\n", str)

                oldStr := str
                for i := 0; i < 3 && oldStr == str; i++ {
                    str = ParseJSON(content, pathString)
                }

                if !validateURL(str) {
                    return ""
                } else {
                    return str
                }
            } else {
                return str
            }
        }
    } else {
        return ""
    }

    resp.Body.Close()
    return ""
}

func postText(b models.Bot, t string) {

    postURL := "https://api.groupme.com/v3/bots/post"
    postBody := map[string]string {
        "bot_id": b.Key,
        "text": t,
    }

    encoded, _ := json.Marshal(postBody)

    http.Post(postURL, "application/json", bytes.NewReader(encoded))
}

func validateURL(u string) bool {

    client := http.Client{}
    if isValidHTTPURLString(u) {
        req, err := http.NewRequest("HEAD", u, nil)
        if err != nil {
            return false
        }

        resp, err := client.Do(req)

        if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 {
            return true
        } else {
            return false
        }
    } else {
        return true
    }

    return true
}

func updateAction(a *models.Action, text string) {
    text = url.QueryEscape(text)

    a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
}<commit_msg>Make sure content type is correct, don't crash when user posts image<commit_after>package handlers

import (
	"fmt"
	"net/http"
    "net/url"
    "io/ioutil"
    "strings"
    "github.com/nelsonleduc/calmanbot/handlers/models"
    "encoding/json"
    "bytes"
    "regexp"
    "sort"
)

func isValidHTTPURLString(s string)  bool {
    URL, _ := url.Parse(s)
    return (URL.Scheme == "http" || URL.Scheme == "https")
}

func HandleCalman(w http.ResponseWriter, r *http.Request) {

    message := ParseMessageJSON(r.Body)
    bot, _ := models.FetchBot(message.GroupID)

    if len(message.Text) < 1 || !strings.HasPrefix(strings.ToLower(message.Text[1:]), strings.ToLower(bot.BotName)) {
        return
    }

    actions, _ := models.FetchActions(true)
    sort.Sort(models.ByPriority(actions))

    var (
        act models.Action
        sMatch string
    )
    for _, a := range actions {
        r, _ := regexp.Compile("(?i)" + *a.Pattern)
        matched := r.FindStringSubmatch(message.Text)
        if len(matched) > 1 && matched[1] != "" {
            sMatch = matched[1]
            act = a
            break
        }
    }

    postString := ""
    for {
        updateAction(&act, sMatch)
        if act.IsURLType() {
            postString = handleURLAction(act, w, bot)
        } else {
            postString = act.Content
        }

        if postString != "" || act.FallbackAction == nil {
            break
        } else {
            act, _ = models.FetchAction(*act.FallbackAction)
        }
    }

    if postString != "" {
        fmt.Printf("Action: %v\n", act.Content)
        fmt.Printf("Posting: %v\n", postString)
        postText(bot, postString)
    }
}

func handleURLAction(a models.Action, w http.ResponseWriter, b models.Bot) string {

    fmt.Fprintln(w, a)
    resp, err := http.Get(a.Content)

    if err == nil {

        content, _ := ioutil.ReadAll(resp.Body)
        pathString := *a.DataPath

        str := ParseJSON(content, pathString)
        if str == "" {
            return ""
        } else {

            if !validateURL(str) {
                fmt.Printf("Invalid URL: %v\n", str)

                oldStr := str
                for i := 0; i < 3 && oldStr == str; i++ {
                    str = ParseJSON(content, pathString)
                }

                if !validateURL(str) {
                    return ""
                } else {
                    return str
                }
            } else {
                return str
            }
        }
    } else {
        return ""
    }

    resp.Body.Close()
    return ""
}

func postText(b models.Bot, t string) {

    postURL := "https://api.groupme.com/v3/bots/post"
    postBody := map[string]string {
        "bot_id": b.Key,
        "text": t,
    }

    encoded, _ := json.Marshal(postBody)

    http.Post(postURL, "application/json", bytes.NewReader(encoded))
}

func validateURL(u string) bool {

    client := http.Client{}
    if isValidHTTPURLString(u) {
        req, err := http.NewRequest("HEAD", u, nil)
        if err != nil {
            return false
        }

        resp, err := client.Do(req)

        if err == nil && resp.StatusCode >= 200 && resp.StatusCode < 300 && strings.HasPrefix(resp.Header.Get("Content-Type"), "image") {
            return true
        } else {
            return false
        }
    } else {
        return true
    }

    return true
}

func updateAction(a *models.Action, text string) {
    text = url.QueryEscape(text)

    a.Content = strings.Replace(a.Content, "{_text_}", text, -1)
}<|endoftext|>


<commit_before>package htmlwriter

import (
	"bytes"
)

type HtmlNode struct {
	name      string
	text      string
	attr      map[string]string
	child     []*HtmlNode
	classHash map[string]string
	styleHash map[string]string
	parents   *HtmlNode
}

func CreateHtmlNode(name string) *HtmlNode {
	return &HtmlNode{name, "", make(map[string]string), make([]*HtmlNode, 0, 100), make(map[string]string), make(map[string]string), nil}
}

func (n *HtmlNode) Add(name string) *HtmlNode {
	c := CreateHtmlNode(name)
	n.child = append(n.child, c)
	c.parents = n
	return c
}
func (n *HtmlNode) InsertAfter(c *HtmlNode) *HtmlNode {
	if n.parents != nil {
		n.detach()
	}

	if len(c.parents.child) > 0 {
		newChild := make([]*HtmlNode, 0, len(c.parents.child)+1)
		for _, v := range c.parents.child {
			if v != c {
				newChild = append(newChild, v)
			} else {
				newChild = append(newChild, c)
				newChild = append(newChild, n)
			}
		}
		c.parents.child = newChild
		n.parents = c.parents
	}
	return n
}

func (n *HtmlNode) Eq(idx int) *HtmlNode {
	if len(n.child) > idx {
		return n.child[idx]
	}
	return nil
}

func (n *HtmlNode) Append(c *HtmlNode) *HtmlNode {
	if c.parents != nil {
		c.detach()
	}
	n.child = append(n.child, c)
	c.parents = n
	return n
}
func (c *HtmlNode) AppendTo(n *HtmlNode) *HtmlNode {
	n.append(c)
	return c
}

func (n *HtmlNode) Detach() *HtmlNode {
	return n.parents.remove(n)
}
func (n *HtmlNode) Remove(c *HtmlNode) *HtmlNode {
	if len(n.child) > 0 {
		newChild := make([]*HtmlNode, 0, len(n.child)-1)
		for _, v := range n.child {
			if v != c {
				newChild = append(newChild, v)
			}
		}
		n.child = newChild
		c.parents = nil
	}
	return n
}

func (n *HtmlNode) SetText(text string) *HtmlNode {
	n.text = text
	return n
}

func (n *HtmlNode) Write(buffer *bytes.Buffer) {
	buffer.WriteString("<")
	buffer.WriteString(n.name)

	if len(n.classHash) > 0 {
		isFirst := true
		buffer.WriteString(" class=\"")
		for v := range n.classHash {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(v)
		}
		buffer.WriteString("\"")
	}

	if len(n.styleHash) > 0 {
		isFirst := true
		buffer.WriteString(" style=\"")
		for k, v := range n.styleHash {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(k)
			buffer.WriteString(":")
			buffer.WriteString(v)
			buffer.WriteString(";")
		}
		buffer.WriteString("\"")
	}

	if len(n.attr) > 0 {
		isFirst := true
		for k, v := range n.attr {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(" ")
			buffer.WriteString(k)
			buffer.WriteString("=\"")
			buffer.WriteString(v)
			buffer.WriteString("\"")
		}
	}

	buffer.WriteString(">")
	if len(n.text) > 0 {
		buffer.WriteString(n.text)
	}
	for _, v := range n.child {
		v.Write(buffer)
	}
	if n.name != "br" {
		buffer.WriteString("</")
		buffer.WriteString(n.name)
		buffer.WriteString(">")
	}
}

func (n *HtmlNode) Class(class string) *HtmlNode {
	ch := n.classHash
	ch[class] = class
	return n
}
func (n *HtmlNode) RemoveClass(class string) *HtmlNode {
	delete(n.classHash, class)
	return n
}

func (n *HtmlNode) Style(style string, value string) *HtmlNode {
	n.styleHash[style] = value
	return n
}
func (n *HtmlNode) RemoveStyle(style string) *HtmlNode {
	delete(n.styleHash, style)
	return n
}

func (n *HtmlNode) Attr(key string, value string) *HtmlNode {
	n.attr[key] = value
	return n
}
func (n *HtmlNode) RemoveAttr(key string) *HtmlNode {
	delete(n.attr, key)
	return n
}
<commit_msg>bug fix<commit_after>package htmlwriter

import (
	"bytes"
)

type HtmlNode struct {
	name      string
	text      string
	attr      map[string]string
	child     []*HtmlNode
	classHash map[string]string
	styleHash map[string]string
	parents   *HtmlNode
}

func CreateHtmlNode(name string) *HtmlNode {
	return &HtmlNode{name, "", make(map[string]string), make([]*HtmlNode, 0, 100), make(map[string]string), make(map[string]string), nil}
}

func (n *HtmlNode) Add(name string) *HtmlNode {
	c := CreateHtmlNode(name)
	n.child = append(n.child, c)
	c.parents = n
	return c
}
func (n *HtmlNode) InsertAfter(c *HtmlNode) *HtmlNode {
	if n.parents != nil {
		n.Detach()
	}

	if len(c.parents.child) > 0 {
		newChild := make([]*HtmlNode, 0, len(c.parents.child)+1)
		for _, v := range c.parents.child {
			if v != c {
				newChild = append(newChild, v)
			} else {
				newChild = append(newChild, c)
				newChild = append(newChild, n)
			}
		}
		c.parents.child = newChild
		n.parents = c.parents
	}
	return n
}

func (n *HtmlNode) Eq(idx int) *HtmlNode {
	if len(n.child) > idx {
		return n.child[idx]
	}
	return nil
}

func (n *HtmlNode) Append(c *HtmlNode) *HtmlNode {
	if c.parents != nil {
		c.Detach()
	}
	n.child = append(n.child, c)
	c.parents = n
	return n
}
func (c *HtmlNode) AppendTo(n *HtmlNode) *HtmlNode {
	n.Append(c)
	return c
}

func (n *HtmlNode) Detach() *HtmlNode {
	return n.parents.Remove(n)
}
func (n *HtmlNode) Remove(c *HtmlNode) *HtmlNode {
	if len(n.child) > 0 {
		newChild := make([]*HtmlNode, 0, len(n.child)-1)
		for _, v := range n.child {
			if v != c {
				newChild = append(newChild, v)
			}
		}
		n.child = newChild
		c.parents = nil
	}
	return n
}

func (n *HtmlNode) SetText(text string) *HtmlNode {
	n.text = text
	return n
}

func (n *HtmlNode) Write(buffer *bytes.Buffer) {
	buffer.WriteString("<")
	buffer.WriteString(n.name)

	if len(n.classHash) > 0 {
		isFirst := true
		buffer.WriteString(" class=\"")
		for v := range n.classHash {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(v)
		}
		buffer.WriteString("\"")
	}

	if len(n.styleHash) > 0 {
		isFirst := true
		buffer.WriteString(" style=\"")
		for k, v := range n.styleHash {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(k)
			buffer.WriteString(":")
			buffer.WriteString(v)
			buffer.WriteString(";")
		}
		buffer.WriteString("\"")
	}

	if len(n.attr) > 0 {
		isFirst := true
		for k, v := range n.attr {
			if isFirst {
				isFirst = false
			} else {
				buffer.WriteString(" ")
			}
			buffer.WriteString(" ")
			buffer.WriteString(k)
			buffer.WriteString("=\"")
			buffer.WriteString(v)
			buffer.WriteString("\"")
		}
	}

	buffer.WriteString(">")
	if len(n.text) > 0 {
		buffer.WriteString(n.text)
	}
	for _, v := range n.child {
		v.Write(buffer)
	}
	if n.name != "br" {
		buffer.WriteString("</")
		buffer.WriteString(n.name)
		buffer.WriteString(">")
	}
}

func (n *HtmlNode) Class(class string) *HtmlNode {
	ch := n.classHash
	ch[class] = class
	return n
}
func (n *HtmlNode) RemoveClass(class string) *HtmlNode {
	delete(n.classHash, class)
	return n
}

func (n *HtmlNode) Style(style string, value string) *HtmlNode {
	n.styleHash[style] = value
	return n
}
func (n *HtmlNode) RemoveStyle(style string) *HtmlNode {
	delete(n.styleHash, style)
	return n
}

func (n *HtmlNode) Attr(key string, value string) *HtmlNode {
	n.attr[key] = value
	return n
}
func (n *HtmlNode) RemoveAttr(key string) *HtmlNode {
	delete(n.attr, key)
	return n
}
<|endoftext|>


<commit_before>// +build integration

/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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 integration

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"

	"github.com/pkg/errors"

	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/util"
	"k8s.io/minikube/pkg/util/retry"
)

var tunnelSession StartSession

var (
	hostname = ""
	domain   = "nginx-svc.default.svc.cluster.local."
)

// TestTunnel are functionality `minikube tunnel` tests which can safely share a profile in serial
func TestTunnel(t *testing.T) {
	type validateFunc func(context.Context, *testing.T, string)
	profile := UniqueProfileName("tunnel")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(20))
	defer cancel()
	defer CleanupWithLogs(t, profile, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
			{"Start", validateMinikubeStart},
			{"StartTunnel", validateTunnelStart},
			{"WaitService", validateServiceStable},
			{"AccessDirect", validateAccessDirect},
			{"DNSResolutionByDig", validateDNSDig},
			{"DNSResolutionByDscacheutil", validateDNSDscacheutil},
			{"AccessThroughDNS", validateAccessDNS},
			{"DeleteTunnel", validateTunnelDelete},
		}
		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				tc.validator(ctx, t, profile)
			})
		}
	})
}

// checkRoutePassword skips tunnel test if sudo password required for route
func checkRoutePassword(t *testing.T) {
	if !KicDriver() && runtime.GOOS != "windows" {
		if err := exec.Command("sudo", "-n", "ifconfig").Run(); err != nil {
			t.Skipf("password required to execute 'route', skipping testTunnel: %v", err)
		}
	}
}

// checkDNSForward skips DNS forwarding test if runtime is not supported
func checkDNSForward(t *testing.T) {
	// Not all platforms support DNS forwarding
	if runtime.GOOS != "darwin" {
		t.Skip("DNS forwarding is supported for darwin only now, skipping test DNS forwarding")
	}
}

// getKubeDNSIP returns kube-dns ClusterIP
func getKubeDNSIP(t *testing.T, profile string) string {
	// Load ClusterConfig
	c, err := config.Load(profile)
	if err != nil {
		t.Errorf("failed to load cluster config: %v", err)
	}
	// Get ipNet
	_, ipNet, err := net.ParseCIDR(c.KubernetesConfig.ServiceCIDR)
	if err != nil {
		t.Errorf("failed to parse service CIDR: %v", err)
	}
	// Get kube-dns ClusterIP
	ip, err := util.GetDNSIP(ipNet.String())
	if err != nil {
		t.Errorf("failed to get kube-dns IP: %v", err)
	}

	return "@" + ip.String()
}

// validateMinikubeStart starts minikube cluster
func validateMinikubeStart(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	args := append([]string{"start", "-p", profile, "--memory=1800", "--install-addons=false", "--wait=all"}, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateTunnelStart starts `minikube tunnel`
func validateTunnelStart(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	args := []string{"-p", profile, "tunnel", "--alsologtostderr"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Errorf("failed to start a tunnel: args %q: %v", args, err)
	}
	tunnelSession = *ss
}

// validateServiceStable starts nginx pod, nginx service and waits nginx having loadbalancer ingress IP
func validateServiceStable(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	client, err := kapi.Client(profile)
	if err != nil {
		t.Fatalf("failed to get kubernetes client for %q: %v", profile, err)
	}

	// Start the "nginx" pod.
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "apply", "-f", filepath.Join(*testdataDir, "testsvc.yaml")))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Command(), err)
	}
	if _, err := PodWait(ctx, t, profile, "default", "run=nginx-svc", Minutes(4)); err != nil {
		t.Fatalf("wait: %v", err)
	}

	if err := kapi.WaitForService(client, "default", "nginx-svc", true, 1*time.Second, Minutes(2)); err != nil {
		t.Fatal(errors.Wrap(err, "Error waiting for nginx service to be up"))
	}

	// Wait until the nginx-svc has a loadbalancer ingress IP
	err = wait.PollImmediate(5*time.Second, Minutes(3), func() (bool, error) {
		rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "svc", "nginx-svc", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}"))
		if err != nil {
			return false, err
		}
		if len(rr.Stdout.String()) > 0 {
			hostname = rr.Stdout.String()
			return true, nil
		}
		return false, nil
	})
	if err != nil {
		t.Errorf("nginx-svc svc.status.loadBalancer.ingress never got an IP")

		rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "svc", "nginx-svc"))
		if err != nil {
			t.Errorf("%s failed: %v", rr.Command(), err)
		}
		t.Logf("failed to kubectl get svc nginx-svc:\n%s", rr.Stdout)
	}
}

// validateAccessDirect validates if the test service can be accessed with LoadBalancer IP from host
func validateAccessDirect(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	got := []byte{}
	url := fmt.Sprintf("http://%s", hostname)

	fetch := func() error {
		h := &http.Client{Timeout: time.Second * 10}
		resp, err := h.Get(url)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		if resp.Body == nil {
			return &retry.RetriableError{Err: fmt.Errorf("no body")}
		}
		defer resp.Body.Close()
		got, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		return nil
	}

	// Check if the nginx service can be accessed
	if err := retry.Expo(fetch, 3*time.Second, Minutes(2), 13); err != nil {
		t.Errorf("failed to hit nginx at %q: %v", url, err)
	}

	want := "Welcome to nginx!"
	if strings.Contains(string(got), want) {
		t.Logf("tunnel at %s is working!", url)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, got)
	}
}

// validateDNSDig validates if the DNS forwarding works by dig command DNS lookup
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateDNSDig(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	dnsIP := getKubeDNSIP(t, profile)

	// Check if the dig DNS lookup works toward kube-dns IP
	rr, err := Run(t, exec.CommandContext(ctx, "dig", "+time=5", "+tries=3", dnsIP, domain, "A"))
	// dig command returns its output for stdout only. So we don't check stderr output.
	if err != nil {
		t.Errorf("failed to resolve DNS name: %v", err)
	}

	want := "ANSWER: 1"
	if strings.Contains(rr.Stdout.String(), want) {
		t.Logf("DNS resolution by dig for %s is working!", domain)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, rr.Stdout.String())
	}
}

// validateDNSDscacheutil validates if the DNS forwarding works by dscacheutil command DNS lookup
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateDNSDscacheutil(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	// Check if the dscacheutil DNS lookup works toward target domain
	rr, err := Run(t, exec.CommandContext(ctx, "dscacheutil", "-q", "host", "-a", "name", domain))
	// If dscacheutil cannot lookup dns record, it returns no output. So we don't check stderr output.
	if err != nil {
		t.Errorf("failed to resolve DNS name: %v", err)
	}

	want := hostname
	if strings.Contains(rr.Stdout.String(), want) {
		t.Logf("DNS resolution by dscacheutil for %s is working!", domain)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, rr.Stdout.String())
	}
}

// validateAccessDNS validates if the test service can be accessed with DNS forwarding from host
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateAccessDNS(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	got := []byte{}
	url := fmt.Sprintf("http://%s", domain)

	fetch := func() error {
		h := &http.Client{Timeout: time.Second * 10}
		resp, err := h.Get(url)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		if resp.Body == nil {
			return &retry.RetriableError{Err: fmt.Errorf("no body")}
		}
		defer resp.Body.Close()
		got, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		return nil
	}

	// Access nginx-svc through DNS resolution
	if err := retry.Expo(fetch, 3*time.Second, Seconds(30), 10); err != nil {
		t.Errorf("failed to hit nginx with DNS forwarded %q: %v", url, err)
		// debug: clear DNS cache: https://github.com/kubernetes/minikube/issues/7809
		_, err = Run(t, exec.CommandContext(ctx, "dscacheutil", "-flushcache"))
		if err != nil {
			t.Errorf("failed to clear DNS cache for macOS: %v", err)
		}
		t.Logf("cleared DNS cache for macOS")
	}

	want := "Welcome to nginx!"
	if strings.Contains(string(got), want) {
		t.Logf("tunnel at %s is working!", url)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, got)
	}
}

// validateTunnelDelete stops `minikube tunnel` and deletes minikube cluster
func validateTunnelDelete(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	// Stop tunnel
	tunnelSession.Stop(t)

	// Delete cluster
	args := []string{"delete", "-p", profile, "--alsologtostderr", "-v=5"}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Errorf("failed to delete minikube with args: %q : %v", rr.Command(), err)
	}
}
<commit_msg>add debug for access direct failure & delete unneccesarry debug<commit_after>// +build integration

/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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 integration

import (
	"context"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"testing"
	"time"

	"k8s.io/apimachinery/pkg/util/wait"

	"github.com/pkg/errors"

	"k8s.io/minikube/pkg/kapi"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/util"
	"k8s.io/minikube/pkg/util/retry"
)

var tunnelSession StartSession

var (
	hostname = ""
	domain   = "nginx-svc.default.svc.cluster.local."
)

// TestTunnel are functionality `minikube tunnel` tests which can safely share a profile in serial
func TestTunnel(t *testing.T) {
	type validateFunc func(context.Context, *testing.T, string)
	profile := UniqueProfileName("tunnel")
	ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
	defer CleanupWithLogs(t, profile, cancel)

	// Serial tests
	t.Run("serial", func(t *testing.T) {
		tests := []struct {
			name      string
			validator validateFunc
		}{
			{"Start", validateMinikubeStart},
			{"StartTunnel", validateTunnelStart},
			{"WaitService", validateServiceStable},
			{"AccessDirect", validateAccessDirect},
			{"DNSResolutionByDig", validateDNSDig},
			{"DNSResolutionByDscacheutil", validateDNSDscacheutil},
			{"AccessThroughDNS", validateAccessDNS},
			{"DeleteTunnel", validateTunnelDelete},
		}
		for _, tc := range tests {
			tc := tc
			t.Run(tc.name, func(t *testing.T) {
				tc.validator(ctx, t, profile)
			})
		}
	})
}

// checkRoutePassword skips tunnel test if sudo password required for route
func checkRoutePassword(t *testing.T) {
	if !KicDriver() && runtime.GOOS != "windows" {
		if err := exec.Command("sudo", "-n", "ifconfig").Run(); err != nil {
			t.Skipf("password required to execute 'route', skipping testTunnel: %v", err)
		}
	}
}

// checkDNSForward skips DNS forwarding test if runtime is not supported
func checkDNSForward(t *testing.T) {
	// Not all platforms support DNS forwarding
	if runtime.GOOS != "darwin" {
		t.Skip("DNS forwarding is supported for darwin only now, skipping test DNS forwarding")
	}
}

// getKubeDNSIP returns kube-dns ClusterIP
func getKubeDNSIP(t *testing.T, profile string) string {
	// Load ClusterConfig
	c, err := config.Load(profile)
	if err != nil {
		t.Errorf("failed to load cluster config: %v", err)
	}
	// Get ipNet
	_, ipNet, err := net.ParseCIDR(c.KubernetesConfig.ServiceCIDR)
	if err != nil {
		t.Errorf("failed to parse service CIDR: %v", err)
	}
	// Get kube-dns ClusterIP
	ip, err := util.GetDNSIP(ipNet.String())
	if err != nil {
		t.Errorf("failed to get kube-dns IP: %v", err)
	}

	return "@" + ip.String()
}

// validateMinikubeStart starts minikube cluster
func validateMinikubeStart(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	args := append([]string{"start", "-p", profile, "--memory=1800", "--install-addons=false", "--wait=all"}, StartArgs()...)
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Fatalf("failed to start minikube with args: %q : %v", rr.Command(), err)
	}
}

// validateTunnelStart starts `minikube tunnel`
func validateTunnelStart(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	args := []string{"-p", profile, "tunnel", "--alsologtostderr"}
	ss, err := Start(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Errorf("failed to start a tunnel: args %q: %v", args, err)
	}
	tunnelSession = *ss
}

// validateServiceStable starts nginx pod, nginx service and waits nginx having loadbalancer ingress IP
func validateServiceStable(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	client, err := kapi.Client(profile)
	if err != nil {
		t.Fatalf("failed to get kubernetes client for %q: %v", profile, err)
	}

	// Start the "nginx" pod.
	rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "apply", "-f", filepath.Join(*testdataDir, "testsvc.yaml")))
	if err != nil {
		t.Fatalf("%s failed: %v", rr.Command(), err)
	}
	if _, err := PodWait(ctx, t, profile, "default", "run=nginx-svc", Minutes(4)); err != nil {
		t.Fatalf("wait: %v", err)
	}

	if err := kapi.WaitForService(client, "default", "nginx-svc", true, 1*time.Second, Minutes(2)); err != nil {
		t.Fatal(errors.Wrap(err, "Error waiting for nginx service to be up"))
	}

	// Wait until the nginx-svc has a loadbalancer ingress IP
	err = wait.PollImmediate(5*time.Second, Minutes(3), func() (bool, error) {
		rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "svc", "nginx-svc", "-o", "jsonpath={.status.loadBalancer.ingress[0].ip}"))
		if err != nil {
			return false, err
		}
		if len(rr.Stdout.String()) > 0 {
			hostname = rr.Stdout.String()
			return true, nil
		}
		return false, nil
	})
	if err != nil {
		t.Errorf("nginx-svc svc.status.loadBalancer.ingress never got an IP")

		rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "svc", "nginx-svc"))
		if err != nil {
			t.Errorf("%s failed: %v", rr.Command(), err)
		}
		t.Logf("failed to kubectl get svc nginx-svc:\n%s", rr.Stdout)
	}
}

// validateAccessDirect validates if the test service can be accessed with LoadBalancer IP from host
func validateAccessDirect(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)

	got := []byte{}
	url := fmt.Sprintf("http://%s", hostname)

	fetch := func() error {
		h := &http.Client{Timeout: time.Second * 10}
		resp, err := h.Get(url)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		if resp.Body == nil {
			return &retry.RetriableError{Err: fmt.Errorf("no body")}
		}
		defer resp.Body.Close()
		got, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		return nil
	}

	// Check if the nginx service can be accessed
	if err := retry.Expo(fetch, 3*time.Second, Minutes(2), 13); err != nil {
		t.Errorf("failed to hit nginx at %q: %v", url, err)

		rr, err := Run(t, exec.CommandContext(ctx, "kubectl", "--context", profile, "get", "svc", "nginx-svc"))
		if err != nil {
			t.Errorf("%s failed: %v", rr.Command(), err)
		}
		t.Logf("failed to kubectl get svc nginx-svc:\n%s", rr.Stdout)
	}

	want := "Welcome to nginx!"
	if strings.Contains(string(got), want) {
		t.Logf("tunnel at %s is working!", url)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, got)
	}
}

// validateDNSDig validates if the DNS forwarding works by dig command DNS lookup
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateDNSDig(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	dnsIP := getKubeDNSIP(t, profile)

	// Check if the dig DNS lookup works toward kube-dns IP
	rr, err := Run(t, exec.CommandContext(ctx, "dig", "+time=5", "+tries=3", dnsIP, domain, "A"))
	// dig command returns its output for stdout only. So we don't check stderr output.
	if err != nil {
		t.Errorf("failed to resolve DNS name: %v", err)
	}

	want := "ANSWER: 1"
	if strings.Contains(rr.Stdout.String(), want) {
		t.Logf("DNS resolution by dig for %s is working!", domain)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, rr.Stdout.String())
	}
}

// validateDNSDscacheutil validates if the DNS forwarding works by dscacheutil command DNS lookup
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateDNSDscacheutil(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	// Check if the dscacheutil DNS lookup works toward target domain
	rr, err := Run(t, exec.CommandContext(ctx, "dscacheutil", "-q", "host", "-a", "name", domain))
	// If dscacheutil cannot lookup dns record, it returns no output. So we don't check stderr output.
	if err != nil {
		t.Errorf("failed to resolve DNS name: %v", err)
	}

	want := hostname
	if strings.Contains(rr.Stdout.String(), want) {
		t.Logf("DNS resolution by dscacheutil for %s is working!", domain)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, rr.Stdout.String())
	}
}

// validateAccessDNS validates if the test service can be accessed with DNS forwarding from host
// NOTE: DNS forwarding is experimental: https://minikube.sigs.k8s.io/docs/handbook/accessing/#dns-resolution-experimental
func validateAccessDNS(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	checkDNSForward(t)

	got := []byte{}
	url := fmt.Sprintf("http://%s", domain)

	fetch := func() error {
		h := &http.Client{Timeout: time.Second * 10}
		resp, err := h.Get(url)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		if resp.Body == nil {
			return &retry.RetriableError{Err: fmt.Errorf("no body")}
		}
		defer resp.Body.Close()
		got, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return &retry.RetriableError{Err: err}
		}
		return nil
	}

	// Access nginx-svc through DNS resolution
	if err := retry.Expo(fetch, 3*time.Second, Seconds(30), 10); err != nil {
		t.Errorf("failed to hit nginx with DNS forwarded %q: %v", url, err)
	}

	want := "Welcome to nginx!"
	if strings.Contains(string(got), want) {
		t.Logf("tunnel at %s is working!", url)
	} else {
		t.Errorf("expected body to contain %q, but got *%q*", want, got)
	}
}

// validateTunnelDelete stops `minikube tunnel` and deletes minikube cluster
func validateTunnelDelete(ctx context.Context, t *testing.T, profile string) {
	checkRoutePassword(t)
	// Stop tunnel
	tunnelSession.Stop(t)

	// Delete cluster
	args := []string{"delete", "-p", profile, "--alsologtostderr", "-v=5"}
	rr, err := Run(t, exec.CommandContext(ctx, Target(), args...))
	if err != nil {
		t.Errorf("failed to delete minikube with args: %q : %v", rr.Command(), err)
	}
}
<|endoftext|>


<commit_before>package server

import (
	"log"
	"net"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/VividCortex/ewma"
	"github.com/klauspost/shutdown"
)

// A Backend is a single running backend instance.
// It will monitor itself and update health and stats every second.
type Backend interface {
	Transport() http.RoundTripper // Returns a transport for the backend
	ID() string                   // A string identifier of this specific backend
	Name() string                 // A name for this backend
	Host() string                 // Returns the hostname of the backend
	Healthy() bool                // Is the backend healthy?
	Statistics() Stats            // Returns a copy of the latest statistics. Updated every second.
	Connections() int             // Return the current number of connections
	Close()                       // Close the backend (before shutdown/reload).
}

// backend is a common base used for sharing functionality
// between different backend types, so implementing different
// ones are easier.
type backend struct {
	rt           *statRT
	healthClient *http.Client
	closeMonitor chan chan struct{}
	Stats        Stats
	ServerHost   string
	HealthURL    string
}

// newBackend returns a new generic backend.
// It will start monitoring the backend at once
func newBackend(bec BackendConfig, serverHost, healthURL string) *backend {
	b := &backend{
		ServerHost: serverHost,
		HealthURL:  healthURL,
	}
	// Create a transport that is used for health checks.
	tr := &http.Transport{
		Dial: (&net.Dialer{
			Timeout:   time.Duration(bec.HealthTimeout),
			KeepAlive: 0,
		}).Dial,
		DisableKeepAlives:  true,
		DisableCompression: true,
	}
	b.healthClient = &http.Client{Transport: tr}

	// Reset running stats.
	b.Stats.Latency = ewma.NewMovingAverage(float64(bec.LatencyAvg))
	b.Stats.FailureRate = ewma.NewMovingAverage(10)

	// Set up the backend transport.
	tr = &http.Transport{
		Dial: func(network, addr string) (net.Conn, error) {
			return net.DialTimeout(network, addr, time.Duration(bec.DialTimeout))
		},
		Proxy: http.ProxyFromEnvironment,
	}
	b.rt = newStatTP(tr)

	// If we have no health url, assume healthy
	if healthURL == "" {
		b.Stats.Healthy = true
	}

	if !bec.DisableHealth {
		b.closeMonitor = make(chan chan struct{}, 0)
		go b.startMonitor()
	}
	return b
}

// startMonitor will monitor stats of the backend
// Will at times require BOTH rt and Stats mutex.
// This means that no other goroutine should acquire
// both at the same time.
func (b *backend) startMonitor() {
	s := b.rt
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	exit := shutdown.First()
	end := b.closeMonitor
	previous := time.Now()

	for {
		select {
		case <-ticker.C:
			elapsed := time.Now().Sub(previous)
			previous = time.Now()
			s.mu.Lock()
			b.Stats.mu.Lock()
			if s.requests == 0 {
				b.Stats.Latency.Add(0)
				b.Stats.FailureRate.Add(0)
			} else {
				b.Stats.Latency.Add(float64(s.latencySum) / float64(elapsed) / float64(s.requests))
				b.Stats.FailureRate.Add(float64(s.errors) / float64(s.requests))
			}
			s.requests = 0
			s.errors = 0
			s.latencySum = 0
			s.mu.Unlock()

			// Perform health check
			b.healthCheck()

			if b.Stats.Healthy && b.Stats.healthFailures > 5 {
				log.Println("5 Consequtive health tests failed. Marking as unhealty.")
				b.Stats.Healthy = false
			}
			if !b.Stats.Healthy && b.Stats.healthFailures == 0 {
				log.Println("Health check succeeded. Marking as healty")
				b.Stats.Healthy = true
			}
			b.Stats.mu.Unlock()
		case n := <-end:
			exit.Cancel()
			close(n)
			return
		case n := <-exit:
			close(n)
			return
		}
	}
}

// healthCheck will check the health by connecting
// to the healthURL of the backend.
// This is called by healthCheck every second.
// It assumes b.Stats.mu is locked, but will unlock it while
// the request is running.
func (b *backend) healthCheck() {
	// If no checkurl har been set, assume we are healthy
	if b.HealthURL == "" {
		b.Stats.Healthy = true
		return
	}

	req, err := http.NewRequest("GET", b.HealthURL, nil)
	if err != nil {
		log.Println("Error checking health of", b.HealthURL, "Error:", err)
	}

	req.Header.Set("User-Agent", "doproxy health checker")

	b.Stats.mu.Unlock()
	// Perform the check
	resp, err := b.healthClient.Do(req)

	b.Stats.mu.Lock()
	// Check response
	if err != nil {
		b.Stats.healthFailures++
		log.Println("Error checking health of", b.HealthURL, "Error:", err)
		return
	}
	if resp.StatusCode >= 500 {
		b.Stats.healthFailures++
		log.Println("Error checking health of", b.HealthURL, "Status code:", resp.StatusCode)
	} else {
		// Reset failures
		b.Stats.healthFailures = 0
	}
	resp.Body.Close()
}

// Transport returns a RoundTripper that will collect stats
// about the backend.
func (b *backend) Transport() http.RoundTripper {
	return b.rt
}

// Healthy returns the healthy state of the backend
func (b *backend) Healthy() bool {
	b.Stats.mu.RLock()
	ok := b.Stats.Healthy
	b.Stats.mu.RUnlock()
	return ok
}

// Healthy returns the healthy state of the backend
func (b *backend) Statistics() Stats {
	b.Stats.mu.RLock()
	s := b.Stats
	b.Stats.mu.RUnlock()
	return s
}

// Host returns the host address of the backend.
func (b *backend) Host() string {
	return b.ServerHost
}

// Close the backend, which will shut down monitoring
// of the backend.
func (b *backend) Close() {
	if b.closeMonitor == nil {
		return
	}
	c := make(chan struct{})
	b.closeMonitor <- c
	<-c
	close(b.closeMonitor)
	b.closeMonitor = nil
}

// Connections returns the number of currently running requests.
// Does not include websocket connections.
func (b *backend) Connections() int {
	b.rt.mu.RLock()
	n := b.rt.running
	b.rt.mu.RUnlock()
	return n
}

func (s *statRT) RoundTrip(req *http.Request) (*http.Response, error) {
	// Record this request as running
	s.mu.Lock()
	s.running++
	s.mu.Unlock()

	// Time the request roundtrip time
	start := time.Now()
	resp, err := s.rt.RoundTrip(req)
	dur := start.Sub(time.Now())

	// Update stats
	s.mu.Lock()
	defer s.mu.Unlock()
	s.running--
	s.requests++
	s.latencySum += dur
	if err != nil {
		s.errors++
		return nil, err
	}
	// Any status code above or equal to 500 is recorded as an error.
	if resp.StatusCode >= 500 {
		s.errors++
		return resp, nil
	}
	return resp, nil
}

// Stats contain regularly updated statistics about a
// backend. To access be sure to hold the 'mu' mutex.
type Stats struct {
	mu             sync.RWMutex
	healthFailures int // Number of total health check failures
	Healthy        bool
	Latency        ewma.MovingAverage
	FailureRate    ewma.MovingAverage
}

// statRT wraps a http.RoundTripper around statistics that can
// be used for load balancing.
type statRT struct {
	rt         http.RoundTripper
	mu         sync.RWMutex
	latencySum time.Duration
	running    int
	requests   int
	errors     int
}

// dropletBackend is a a backend instance with a DigitalOcean droplet
// behind it.
type DropletBackend struct {
	*backend
	Droplet Droplet
}

// NewDropletBackend returns a Backend configured with the
// Droplet information.
func NewDropletBackend(d Droplet, bec BackendConfig) Backend {
	b := &DropletBackend{
		backend: newBackend(bec, d.ServerHost, d.HealthURL),
		Droplet: d,
	}
	return b
}

// ID returns a unique ID of this backend
func (d *DropletBackend) ID() string {
	return strconv.Itoa(d.Droplet.ID)
}

// ID returns a name of this backend
func (d *DropletBackend) Name() string {
	return d.Droplet.Name
}

func newStatTP(rt http.RoundTripper) *statRT {
	s := &statRT{rt: rt}
	return s
}
<commit_msg>Return a pointer to stats, since it contains a lock.<commit_after>package server

import (
	"log"
	"net"
	"net/http"
	"strconv"
	"sync"
	"time"

	"github.com/VividCortex/ewma"
	"github.com/klauspost/shutdown"
)

// A Backend is a single running backend instance.
// It will monitor itself and update health and stats every second.
type Backend interface {
	Transport() http.RoundTripper // Returns a transport for the backend
	ID() string                   // A string identifier of this specific backend
	Name() string                 // A name for this backend
	Host() string                 // Returns the hostname of the backend
	Healthy() bool                // Is the backend healthy?
	Statistics() *Stats           // Returns a copy of the latest statistics. Updated every second.
	Connections() int             // Return the current number of connections
	Close()                       // Close the backend (before shutdown/reload).
}

// backend is a common base used for sharing functionality
// between different backend types, so implementing different
// ones are easier.
type backend struct {
	rt           *statRT
	healthClient *http.Client
	closeMonitor chan chan struct{}
	Stats        Stats
	ServerHost   string
	HealthURL    string
}

// newBackend returns a new generic backend.
// It will start monitoring the backend at once
func newBackend(bec BackendConfig, serverHost, healthURL string) *backend {
	b := &backend{
		ServerHost: serverHost,
		HealthURL:  healthURL,
	}
	// Create a transport that is used for health checks.
	tr := &http.Transport{
		Dial: (&net.Dialer{
			Timeout:   time.Duration(bec.HealthTimeout),
			KeepAlive: 0,
		}).Dial,
		DisableKeepAlives:  true,
		DisableCompression: true,
	}
	b.healthClient = &http.Client{Transport: tr}

	// Reset running stats.
	b.Stats.Latency = ewma.NewMovingAverage(float64(bec.LatencyAvg))
	b.Stats.FailureRate = ewma.NewMovingAverage(10)

	// Set up the backend transport.
	tr = &http.Transport{
		Dial: func(network, addr string) (net.Conn, error) {
			return net.DialTimeout(network, addr, time.Duration(bec.DialTimeout))
		},
		Proxy: http.ProxyFromEnvironment,
	}
	b.rt = newStatTP(tr)

	// If we have no health url, assume healthy
	if healthURL == "" {
		b.Stats.Healthy = true
	}

	if !bec.DisableHealth {
		b.closeMonitor = make(chan chan struct{}, 0)
		go b.startMonitor()
	}
	return b
}

// startMonitor will monitor stats of the backend
// Will at times require BOTH rt and Stats mutex.
// This means that no other goroutine should acquire
// both at the same time.
func (b *backend) startMonitor() {
	s := b.rt
	ticker := time.NewTicker(time.Second)
	defer ticker.Stop()
	exit := shutdown.First()
	end := b.closeMonitor
	previous := time.Now()

	for {
		select {
		case <-ticker.C:
			elapsed := time.Now().Sub(previous)
			previous = time.Now()
			s.mu.Lock()
			b.Stats.mu.Lock()
			if s.requests == 0 {
				b.Stats.Latency.Add(0)
				b.Stats.FailureRate.Add(0)
			} else {
				b.Stats.Latency.Add(float64(s.latencySum) / float64(elapsed) / float64(s.requests))
				b.Stats.FailureRate.Add(float64(s.errors) / float64(s.requests))
			}
			s.requests = 0
			s.errors = 0
			s.latencySum = 0
			s.mu.Unlock()

			// Perform health check
			b.healthCheck()

			if b.Stats.Healthy && b.Stats.healthFailures > 5 {
				log.Println("5 Consequtive health tests failed. Marking as unhealty.")
				b.Stats.Healthy = false
			}
			if !b.Stats.Healthy && b.Stats.healthFailures == 0 {
				log.Println("Health check succeeded. Marking as healty")
				b.Stats.Healthy = true
			}
			b.Stats.mu.Unlock()
		case n := <-end:
			exit.Cancel()
			close(n)
			return
		case n := <-exit:
			close(n)
			return
		}
	}
}

// healthCheck will check the health by connecting
// to the healthURL of the backend.
// This is called by healthCheck every second.
// It assumes b.Stats.mu is locked, but will unlock it while
// the request is running.
func (b *backend) healthCheck() {
	// If no checkurl har been set, assume we are healthy
	if b.HealthURL == "" {
		b.Stats.Healthy = true
		return
	}

	req, err := http.NewRequest("GET", b.HealthURL, nil)
	if err != nil {
		log.Println("Error checking health of", b.HealthURL, "Error:", err)
	}

	req.Header.Set("User-Agent", "doproxy health checker")

	b.Stats.mu.Unlock()
	// Perform the check
	resp, err := b.healthClient.Do(req)

	b.Stats.mu.Lock()
	// Check response
	if err != nil {
		b.Stats.healthFailures++
		log.Println("Error checking health of", b.HealthURL, "Error:", err)
		return
	}
	if resp.StatusCode >= 500 {
		b.Stats.healthFailures++
		log.Println("Error checking health of", b.HealthURL, "Status code:", resp.StatusCode)
	} else {
		// Reset failures
		b.Stats.healthFailures = 0
	}
	resp.Body.Close()
}

// Transport returns a RoundTripper that will collect stats
// about the backend.
func (b *backend) Transport() http.RoundTripper {
	return b.rt
}

// Healthy returns the healthy state of the backend
func (b *backend) Healthy() bool {
	b.Stats.mu.RLock()
	ok := b.Stats.Healthy
	b.Stats.mu.RUnlock()
	return ok
}

// Healthy returns the healthy state of the backend
func (b *backend) Statistics() *Stats {
	b.Stats.mu.RLock()
	s := b.Stats
	b.Stats.mu.RUnlock()
	return &s
}

// Host returns the host address of the backend.
func (b *backend) Host() string {
	return b.ServerHost
}

// Close the backend, which will shut down monitoring
// of the backend.
func (b *backend) Close() {
	if b.closeMonitor == nil {
		return
	}
	c := make(chan struct{})
	b.closeMonitor <- c
	<-c
	close(b.closeMonitor)
	b.closeMonitor = nil
}

// Connections returns the number of currently running requests.
// Does not include websocket connections.
func (b *backend) Connections() int {
	b.rt.mu.RLock()
	n := b.rt.running
	b.rt.mu.RUnlock()
	return n
}

func (s *statRT) RoundTrip(req *http.Request) (*http.Response, error) {
	// Record this request as running
	s.mu.Lock()
	s.running++
	s.mu.Unlock()

	// Time the request roundtrip time
	start := time.Now()
	resp, err := s.rt.RoundTrip(req)
	dur := start.Sub(time.Now())

	// Update stats
	s.mu.Lock()
	defer s.mu.Unlock()
	s.running--
	s.requests++
	s.latencySum += dur
	if err != nil {
		s.errors++
		return nil, err
	}
	// Any status code above or equal to 500 is recorded as an error.
	if resp.StatusCode >= 500 {
		s.errors++
		return resp, nil
	}
	return resp, nil
}

// Stats contain regularly updated statistics about a
// backend. To access be sure to hold the 'mu' mutex.
type Stats struct {
	mu             sync.RWMutex
	healthFailures int // Number of total health check failures
	Healthy        bool
	Latency        ewma.MovingAverage
	FailureRate    ewma.MovingAverage
}

// statRT wraps a http.RoundTripper around statistics that can
// be used for load balancing.
type statRT struct {
	rt         http.RoundTripper
	mu         sync.RWMutex
	latencySum time.Duration
	running    int
	requests   int
	errors     int
}

// dropletBackend is a a backend instance with a DigitalOcean droplet
// behind it.
type DropletBackend struct {
	*backend
	Droplet Droplet
}

// NewDropletBackend returns a Backend configured with the
// Droplet information.
func NewDropletBackend(d Droplet, bec BackendConfig) Backend {
	b := &DropletBackend{
		backend: newBackend(bec, d.ServerHost, d.HealthURL),
		Droplet: d,
	}
	return b
}

// ID returns a unique ID of this backend
func (d *DropletBackend) ID() string {
	return strconv.Itoa(d.Droplet.ID)
}

// ID returns a name of this backend
func (d *DropletBackend) Name() string {
	return d.Droplet.Name
}

func newStatTP(rt http.RoundTripper) *statRT {
	s := &statRT{rt: rt}
	return s
}
<|endoftext|>


<commit_before>package httpapi

import (
	"bytes"
	"fmt"
	"github.com/HouzuoGuo/tiedot/data"
	"github.com/HouzuoGuo/tiedot/db"
	"math/rand"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

var (
	requestInsertWithoutDoc = fmt.Sprintf("http://localhost:8080/insert?col=%s", collection)
	requestInsertWithoutCol = "http://localhost:8080/insert"

	requestGet       = "http://localhost:8080/get?col=%s&id=%s"
	requestGetNotCol = "http://localhost:8080/get?id=%s"
	requestGetNotId  = "http://localhost:8080/get?col=%s"

	requestGetPageNotCol   = "http://localhost:8080/getpage"
	requestGetPageNotPage  = "http://localhost:8080/getpage?page=%s"
	requestGetPageNotTotal = "http://localhost:8080/getpage?col=%s&page=%s"
)

// Test Insert
func TestInsertNotExistParamCol(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	req := httptest.NewRequest("POST", requestInsertWithoutCol, nil)
	w := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Insert(w, req)

	if w.Code != 400 || strings.TrimSpace(w.Body.String()) != "Please pass POST/PUT/GET parameter value of 'col'." {
		t.Error("Expected code 400 and message error parameter col not exist")
	}
}
func TestInsertEmptyDoc(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("")
	reqCreate := httptest.NewRequest("POST", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != "Please pass POST/PUT/GET parameter value of 'doc'." {
		t.Error("Expected code 400 and message error not exist parameter 'doc'")
	}
}
func TestInsertErrorUnmarshal(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("doc='{\"a\": 1, \"b\": 2}'")
	reqCreate := httptest.NewRequest("POST", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != "'doc='{\"a\": 1, \"b\": 2}'' is not valid JSON document." {
		t.Error("Expected code 400 and message error not valid json")
	}
}
func TestInsert(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	_, err = strconv.Atoi(strings.TrimSpace(wInsert.Body.String()))

	if wInsert.Code != 201 || err != nil {
		t.Error("Expected code 201 and get id new record")
	}
}
func TestInsertCollectionNotExist(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != fmt.Sprintf("Collection '%s' does not exist.", collection) {
		t.Error("Expected code 400 and message error not exist is collection.")
	}
}
func TestInsertError(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	sizeByte := data.DOC_MAX_ROOM
	stringJson := fmt.Sprintf("{\"a\": 1, \"b\": \"%s\"}", RandStringBytes(sizeByte))
	b := bytes.NewBuffer([]byte(stringJson))
	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wInsert := httptest.NewRecorder()
	wCreate := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 500 || strings.TrimSpace(wInsert.Body.String()) != "Document is too large. Max: `2097152`, Given: `4194332`" {
		t.Error("Expected code 500 and message document is too large.")
	}
}

// Test Get
func TestGet(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)

	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, strings.TrimSpace(wInsert.Body.String())), b)

	Get(wGet, reqGet)

	if wGet.Code != 200 || strings.TrimSpace(wGet.Body.String()) != strings.Replace(jsonStr, "\\", "", -1) {
		t.Error("Expected code 200 and get document from collection")
	}
}
func TestGetInvalidId(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)

	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)

	randIntStr := RandStringBytes(rand.Intn(5))

	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, randIntStr), b)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("Invalid document ID '%s'.", randIntStr) {
		t.Error("Expected code 404 and message error invalid document ID")
	}
}
func TestGetCollectionNotExist(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())
	collectionFake := "fake"
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collectionFake, randIntStr), b)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("Collection '%s' does not exist.", collectionFake) {
		t.Error("Expected code 400 and message error not exist collection")
	}
}
func TestGetNoSuchDocument(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, randIntStr), b)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 404 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("No such document ID %s.", randIntStr) {
		t.Error("Expected code 404 and message error not such document")
	}
}
func TestGetNotParamCol(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGetNotCol, randIntStr), nil)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != "Please pass POST/PUT/GET parameter value of 'col'." {
		t.Error("Expected code 400 and message error not such param 'col'")
	}
}
func TestGetNotParamId(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGetNotId, collection), nil)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != "Please pass POST/PUT/GET parameter value of 'id'." {
		t.Error("Expected code 400 and message error not such param 'id'")
	}
}

// Test GetPage
func TestGetPage(t *testing.T) {
	//requestGetPageNotCol
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	_, err = strconv.Atoi(strings.TrimSpace(wInsert.Body.String()))

	if wInsert.Code != 201 || err != nil {
		t.Error("Expected code 201 and get id new record")
	}
}
<commit_msg>test getpage in httpapi<commit_after>package httpapi

import (
	"bytes"
	"fmt"
	"github.com/HouzuoGuo/tiedot/data"
	"github.com/HouzuoGuo/tiedot/db"
	"math/rand"
	"net/http/httptest"
	"strconv"
	"strings"
	"testing"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

var (
	requestInsertWithoutDoc = fmt.Sprintf("http://localhost:8080/insert?col=%s", collection)
	requestInsertWithoutCol = "http://localhost:8080/insert"

	requestGet       = "http://localhost:8080/get?col=%s&id=%s"
	requestGetNotCol = "http://localhost:8080/get?id=%s"
	requestGetNotId  = "http://localhost:8080/get?col=%s"

	requestGetPageNotCol   = "http://localhost:8080/getpage"
	requestGetPageNotPage  = "http://localhost:8080/getpage?col=%s"
	requestGetPageNotTotal = "http://localhost:8080/getpage?col=%s&page=%s"
	requestGetPage         = "http://localhost:8080/getpage?col=%s&page=%s&total=%d"

	page  = "1"
	total = 2
)

// Test Insert
func TestInsertNotExistParamCol(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	req := httptest.NewRequest("POST", requestInsertWithoutCol, nil)
	w := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Insert(w, req)

	if w.Code != 400 || strings.TrimSpace(w.Body.String()) != "Please pass POST/PUT/GET parameter value of 'col'." {
		t.Error("Expected code 400 and message error parameter col not exist")
	}
}
func TestInsertEmptyDoc(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("")
	reqCreate := httptest.NewRequest("POST", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != "Please pass POST/PUT/GET parameter value of 'doc'." {
		t.Error("Expected code 400 and message error not exist parameter 'doc'")
	}
}
func TestInsertErrorUnmarshal(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("doc='{\"a\": 1, \"b\": 2}'")
	reqCreate := httptest.NewRequest("POST", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != "'doc='{\"a\": 1, \"b\": 2}'' is not valid JSON document." {
		t.Error("Expected code 400 and message error not valid json")
	}
}
func TestInsert(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	_, err = strconv.Atoi(strings.TrimSpace(wInsert.Body.String()))

	if wInsert.Code != 201 || err != nil {
		t.Error("Expected code 201 and get id new record")
	}
}
func TestInsertCollectionNotExist(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wInsert := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Insert(wInsert, reqInsert)

	if wInsert.Code != 400 || strings.TrimSpace(wInsert.Body.String()) != fmt.Sprintf("Collection '%s' does not exist.", collection) {
		t.Error("Expected code 400 and message error not exist is collection.")
	}
}
func TestInsertError(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	sizeByte := data.DOC_MAX_ROOM
	stringJson := fmt.Sprintf("{\"a\": 1, \"b\": \"%s\"}", RandStringBytes(sizeByte))
	b := bytes.NewBuffer([]byte(stringJson))
	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)
	wInsert := httptest.NewRecorder()
	wCreate := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	if wInsert.Code != 500 || strings.TrimSpace(wInsert.Body.String()) != "Document is too large. Max: `2097152`, Given: `4194332`" {
		t.Error("Expected code 500 and message document is too large.")
	}
}

// Test Get
func TestGet(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("POST", requestInsertWithoutDoc, b)

	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, strings.TrimSpace(wInsert.Body.String())), b)

	Get(wGet, reqGet)

	if wGet.Code != 200 || strings.TrimSpace(wGet.Body.String()) != strings.Replace(jsonStr, "\\", "", -1) {
		t.Error("Expected code 200 and get document from collection")
	}
}
func TestGetInvalidId(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	wGet := httptest.NewRecorder()

	randIntStr := RandStringBytes(5)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, randIntStr), b)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("Invalid document ID '%s'.", randIntStr) {
		t.Error("Expected code 404 and message error invalid document ID")
	}
}
func TestGetCollectionNotExist(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())
	collectionFake := "fake"
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collectionFake, randIntStr), b)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("Collection '%s' does not exist.", collectionFake) {
		t.Error("Expected code 400 and message error not exist collection")
	}
}
func TestGetNoSuchDocument(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	b := &bytes.Buffer{}
	jsonStr := "{\"a\":1,\"b\":2}"
	b.WriteString(jsonStr)

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGet, collection, randIntStr), b)
	wCreate := httptest.NewRecorder()
	wGet := httptest.NewRecorder()

	Create(wCreate, reqCreate)
	Get(wGet, reqGet)

	if wGet.Code != 404 || strings.TrimSpace(wGet.Body.String()) != fmt.Sprintf("No such document ID %s.", randIntStr) {
		t.Error("Expected code 404 and message error not such document")
	}
}
func TestGetNotParamCol(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	var err error
	randIntStr := strconv.Itoa(rand.Int())

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGetNotCol, randIntStr), nil)
	wGet := httptest.NewRecorder()

	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != "Please pass POST/PUT/GET parameter value of 'col'." {
		t.Error("Expected code 400 and message error not such param 'col'")
	}
}
func TestGetNotParamId(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()
	var err error

	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}
	reqGet := httptest.NewRequest("POST", fmt.Sprintf(requestGetNotId, collection), nil)
	wGet := httptest.NewRecorder()
	Get(wGet, reqGet)

	if wGet.Code != 400 || strings.TrimSpace(wGet.Body.String()) != "Please pass POST/PUT/GET parameter value of 'id'." {
		t.Error("Expected code 400 and message error not such param 'id'")
	}
}

// Test GetPage
func TestGetPageNotCol(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	reqGetPage := httptest.NewRequest("POST", requestGetPageNotCol, nil)
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != "Please pass POST/PUT/GET parameter value of 'col'." {
		t.Error("Expected code 400 and message error value of 'col'")
	}
}
func TestGetPageNotPage(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	reqGetPage := httptest.NewRequest("POST", fmt.Sprintf(requestGetPageNotPage, collection), nil)
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != "Please pass POST/PUT/GET parameter value of 'page'." {
		t.Error("Expected code 400 and message error value of 'page'")
	}
}
func TestGetPageNotTotal(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	reqGetPage := httptest.NewRequest("POST", fmt.Sprintf(requestGetPageNotTotal, collection, page), nil)
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != "Please pass POST/PUT/GET parameter value of 'total'." {
		t.Error("Expected code 400 and message error value of 'total'")
	}
}
func TestGetPageErrorTotal(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	reqGetPage := httptest.NewRequest("POST", fmt.Sprintf(requestGetPage, collection, page, 0), nil)
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != "Invalid total page number '0'." {
		t.Error("Expected code 400 and message error invalid total page number 0")
	}
}
func TestGetPageErrorPage(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	reqGetPage := httptest.NewRequest("POST", fmt.Sprintf(requestGetPage, collection, "-1", total), nil)
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != "Invalid page number '-1'." {
		t.Error("Expected code 400 and message error invalid page number -1")
	}
}
func TestGetPageCollectionNotExist(t *testing.T) {
		setupTestCase()
		defer tearDownTestCase()

		b := &bytes.Buffer{}
		b.WriteString("{\"a\": 1, \"b\": 2}")

		reqGetPage := httptest.NewRequest("GET", fmt.Sprintf(requestGetPage, collection, page, total), nil)
		wGetPage := httptest.NewRecorder()

		var err error
		if HttpDB, err = db.OpenDB(tempDir); err != nil {
			panic(err)
		}

		GetPage(wGetPage, reqGetPage)

		if wGetPage.Code != 400 || strings.TrimSpace(wGetPage.Body.String()) != fmt.Sprintf("Collection '%s' does not exist.", collection) {
			t.Error("Expected code 400 and message error collection does not exist.")
		}
}
func TestGetPage(t *testing.T) {
	setupTestCase()
	defer tearDownTestCase()

	b := &bytes.Buffer{}
	b.WriteString("{\"a\": 1, \"b\": 2}")

	reqCreate := httptest.NewRequest("GET", requestCreate, nil)
	reqInsert := httptest.NewRequest("GET", requestInsertWithoutDoc, b)
	reqGetPage := httptest.NewRequest("GET", fmt.Sprintf(requestGetPage, collection, page, total), nil)

	wCreate := httptest.NewRecorder()
	wInsert := httptest.NewRecorder()
	wGetPage := httptest.NewRecorder()

	var err error
	if HttpDB, err = db.OpenDB(tempDir); err != nil {
		panic(err)
	}

	Create(wCreate, reqCreate)
	Insert(wInsert, reqInsert)

	GetPage(wGetPage, reqGetPage)

	if wGetPage.Code != 200 {
		t.Error("Expected code 200 and json data")
	}
}

<|endoftext|>