<commit_before>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"testing"
	"time"
)

const (
	ANY_DB_DRIVER = ""
	TEST_ENDPOINT = "localhost:9999"
	TEST_DB       = "goslow_test"
)

var DATA_SOURCE = map[string]string{
	"sqlite3":  DEFAULT_CONFIG.dataSource,
	"postgres": "postgres://localhost/" + TEST_DB,
}

type TestCase struct {
	createDefaultRules bool
	adminUrlPathPrefix string
	driver             string
	dataSource         string
}

type TestCases []*TestCase

type CheckFunc func(*testing.T, *httptest.Server, *TestCase)

var (
	defaultTestCases = TestCases{
		NewTestCase(true, "", ANY_DB_DRIVER),
	}

	ruleCreationTestCases = TestCases{
		NewTestCase(true, "", ANY_DB_DRIVER),
		NewTestCase(false, "/goslow", ANY_DB_DRIVER),
		NewTestCase(false, "/goslow/", ANY_DB_DRIVER),
		NewTestCase(false, "/te", ANY_DB_DRIVER),
		NewTestCase(false, "/te/", ANY_DB_DRIVER),
		NewTestCase(false, "/composite/path", ANY_DB_DRIVER),
	}
)

func NewTestCase(createDefaultRules bool, adminUrlPathPrefix string, driver string) *TestCase {
	dataSource, knownDriver := DATA_SOURCE[driver]
	if driver != ANY_DB_DRIVER && !knownDriver {
		log.Fatalf("unknown driver: <%s>", driver)
	}
	return &TestCase{
		createDefaultRules: createDefaultRules,
		adminUrlPathPrefix: adminUrlPathPrefix,
		driver:             driver,
		dataSource:         dataSource,
	}
}

func TestZeroSite(t *testing.T) {
	runAll(t, checkZeroSite, defaultTestCases)
}

func runAll(t *testing.T, checkFunc CheckFunc, testCases TestCases) {
	for _, testCase := range runnable(all(testCases)) {
		run(t, checkFunc, testCase)
	}
}

func runnable(testCases TestCases) TestCases {
	runnableTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if !testCase.skippable() {
			runnableTestCases = append(runnableTestCases, testCase)
		}
	}
	return runnableTestCases
}

func all(testCases TestCases) TestCases {
	allTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.driver == ANY_DB_DRIVER {
			sqlite3TestCase := NewTestCase(testCase.createDefaultRules, testCase.adminUrlPathPrefix, "sqlite3")
			postgresTestCase := NewTestCase(testCase.createDefaultRules, testCase.adminUrlPathPrefix, "postgres")
			allTestCases = append(allTestCases, sqlite3TestCase)
			allTestCases = append(allTestCases, postgresTestCase)
		} else {
			allTestCases = append(allTestCases, testCase)
		}
	}
	return allTestCases
}

func (testCase *TestCase) skippable() bool {
	return testCase.driver == "postgres" && testing.Short()
}

func run(t *testing.T, checkFunc CheckFunc, testCase *TestCase) {
	if testCase.driver == "postgres" {
		createDb(TEST_DB)
		defer dropDb(TEST_DB)
	}
	goSlowServer := newSubDomainServer(testCase)
	server := httptest.NewServer(goSlowServer)
	defer server.Close()
	defer goSlowServer.storage.db.Close()
	checkFunc(t, server, testCase)
}

func checkZeroSite(t *testing.T, server *httptest.Server, testCase *TestCase) {
	bytesShouldBeEqual(t,
		readBody(GET(server.URL, "/", makeHost("0", TEST_ENDPOINT))),
		DEFAULT_BODY)
}

func TestTooLargeDelay(t *testing.T) {
	runAll(t, checkTooLargeDelay, defaultTestCases)
}

func checkTooLargeDelay(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminUrlPathPrefix
	domain := newDomain(server, join(prefix, "/booya"), []byte("haha"))
	site := getSite(domain)
	resp := addRule(server, &Rule{Site: site, Delay: time.Duration(1000) * time.Second})
	shouldHaveStatusCode(t, http.StatusBadRequest, resp)
	resp = GET(server.URL, "/", domain)
	shouldHaveStatusCode(t, http.StatusNotFound, resp)
}

func TestRedefineBuiltinSites(t *testing.T) {
	runAll(t, checkRedefineBuiltinSites, defaultTestCases)
}

func checkRedefineBuiltinSites(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, site := range []string{"0", "99", "500", "create"} {
		resp := addRule(server, &Rule{Site: site, Path: "/test", Body: []byte("hop"), Method: "GET"})
		shouldHaveStatusCode(t, http.StatusForbidden, resp)
	}
}

func TestRedefineNonExistentSite(t *testing.T) {
	runAll(t, checkRedefineNonExistentSite, defaultTestCases)
}

func checkRedefineNonExistentSite(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, site := range []string{"", "ha", "admin-500"} {
		resp := addRule(server, &Rule{Site: site})
		shouldHaveStatusCode(t, http.StatusNotFound, resp)
	}
}

func TestDelay(t *testing.T) {
	runAll(t, checkDelay, defaultTestCases)
}

func checkDelay(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondIn(t,
		createGET(server.URL, "/", makeHost("0", TEST_ENDPOINT)),
		0, 0.1) // seconds
	shouldRespondIn(t,
		createGET(server.URL, "/", makeHost("1", TEST_ENDPOINT)),
		1, 1.1) // seconds
}

func TestStatus(t *testing.T) {
	runAll(t, checkStatus, defaultTestCases)
}

func checkStatus(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, statusCode := range []int{200, 404, 500} {
		resp := GET(server.URL, "/", makeHost(strconv.Itoa(statusCode), TEST_ENDPOINT))
		shouldHaveStatusCode(t, statusCode, resp)
	}
}

func TestRuleCreation(t *testing.T) {
	runAll(t, checkRuleCreationTestCase, ruleCreationTestCases)
}

func checkRuleCreationTestCase(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminUrlPathPrefix
	isInSingleSiteMode := prefix != ""
	domain, site := TEST_ENDPOINT, ""
	root_body := []byte("haha")
	test_body := []byte("hop")
	test_post_body := []byte("for POST")
	empty_payload := []byte("")

	if isInSingleSiteMode {
		resp := addRule(server, &Rule{Path: join(prefix, "/"), Body: root_body})
		shouldHaveStatusCode(t, http.StatusOK, resp)
	} else {
		domain = newDomain(server, join(prefix, "/"), root_body)
		site = getSite(domain)
	}

	bytesShouldBeEqual(t, readBody(GET(server.URL, "/", domain)), root_body)

	// testing GET rule
	resp := addRule(server, &Rule{Site: site, Path: join(prefix, "/test"), Body: test_body, Method: "GET"})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that GET /test rule works
	bytesShouldBeEqual(t, readBody(GET(server.URL, "/test", domain)), test_body)
	// checking that GET /test doesn't affect POST
	resp = POST(server.URL, "/test", domain, []byte(""))
	intShouldBeEqual(t, 404, resp.StatusCode)

	// testing POST rule
	resp = addRule(server, &Rule{Site: site, Path: join(prefix, "/test"), Body: test_post_body, Method: "POST",
		Delay: time.Duration(100) * time.Millisecond})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that POST rule doesn't affect GET
	bytesShouldBeEqual(t, readBody(GET(server.URL, "/test", domain)), test_body)
	// checking that POST /test rule works
	bytesShouldBeEqual(t, readBody(POST(server.URL, "/test", domain, empty_payload)), test_post_body)
	shouldRespondIn(t, createPOST(server.URL, "/test", domain, empty_payload), 0.1, 0.15)
}

func newSubDomainServer(testCase *TestCase) *Server {
	config := DEFAULT_CONFIG // copies DEFAULT_CONFIG
	config.endpoint = TEST_ENDPOINT
	config.createDefaultRules = testCase.createDefaultRules
	config.adminUrlPathPrefix = testCase.adminUrlPathPrefix
	config.driver = testCase.driver
	config.dataSource = testCase.dataSource
	return NewServer(&config)
}

func GET(url, path, host string) *http.Response {
	req := createGET(url, path, host)
	return do(req)
}

func do(req *http.Request) *http.Response {
	resp, err := new(http.Client).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	return resp
}

func createGET(url, path, host string) *http.Request {
	req, err := http.NewRequest("GET", url+path, nil)
	if err != nil {
		log.Fatal(err)
	}
	req.Host = host
	return req
}

func readBody(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func makeHost(subdomain, host string) string {
	return fmt.Sprintf("%s.%s", subdomain, host)
}

func bytesShouldBeEqual(t *testing.T, expected, actual []byte) {
	if !bytes.Equal(expected, actual) {
		t.Fatalf("<<%v>> != <<%v>>", string(expected), string(actual))
	}
}

func intShouldBeEqual(t *testing.T, expected, actual int) {
	if expected != actual {
		t.Fatalf("<<%v>> != <<%v>>", expected, actual)
	}
}

func shouldRespondIn(t *testing.T, req *http.Request, minSeconds, maxSeconds float64) {
	start := time.Now()
	resp := do(req)
	readBody(resp)
	duration := time.Since(start)
	minDuration := toDuration(minSeconds)
	maxDuration := toDuration(maxSeconds)
	if duration < minDuration || duration > maxDuration {
		t.Fatalf("%s%s answered in %v. Not in the interval [%v; %v]",
			req.Host, req.URL.Path, duration, minDuration, maxDuration)
	}
}

func shouldHaveStatusCode(t *testing.T, statusCode int, resp *http.Response) {
	intShouldBeEqual(t, statusCode, resp.StatusCode)
}

func toDuration(seconds float64) time.Duration {
	return time.Duration(seconds*1000) * time.Millisecond
}

func newDomain(server *httptest.Server, path string, response []byte) string {
	resp := POST(server.URL, fmt.Sprintf("%s?output=short&method=GET", path),
		makeHost("create", TEST_ENDPOINT), response)
	return string(readBody(resp))
}

func POST(url, path, host string, payload []byte) *http.Response {
	req := createPOST(url, path, host, payload)
	return do(req)
}

func createPOST(url, path, host string, payload []byte) *http.Request {
	req, err := http.NewRequest("POST", url+path, bytes.NewReader(payload))
	if err != nil {
		log.Fatal(err)
	}
	req.Host = host
	return req
}

func getSite(domain string) string {
	return strings.Split(domain, ".")[0]
}

func addRule(server *httptest.Server, rule *Rule) *http.Response {
	req := createPOST(server.URL, rule.Path, makeHost("admin-"+rule.Site, TEST_ENDPOINT),
		rule.Body)
	req.URL.RawQuery = getQueryString(rule)
	return do(req)
}

func getQueryString(rule *Rule) string {
	params := url.Values{}
	params.Set("method", rule.Method)
	params.Set("delay", fmt.Sprintf("%f", rule.Delay.Seconds()))
	return params.Encode()
}

// Wrapper around path.Join. Preserves trailing slash.
func join(elem ...string) string {
	lastElem := elem[len(elem)-1]
	shouldEndWithSlash := strings.HasSuffix(lastElem, "/")
	joined := path.Join(elem...)
	if shouldEndWithSlash && !strings.HasSuffix(joined, "/") {
		joined += "/"
	}
	return joined
}

func createDb(name string) {
	cmd := exec.Command("createdb", name)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("createdb error: %s", err)
	}
}

func dropDb(name string) {
	cmd := exec.Command("dropdb", name)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("dropdb error: %s", err)
	}
}
<commit_msg>Refactor server_test.go<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"testing"
	"time"
)

const (
	ANY_DB_DRIVER = ""
	TEST_ENDPOINT = "localhost:9999"
	TEST_DB       = "goslow_test"
)

var DATA_SOURCE = map[string]string{
	"sqlite3":  DEFAULT_CONFIG.dataSource,
	"postgres": "postgres://localhost/" + TEST_DB,
}

type TestCase struct {
	createDefaultRules bool
	adminUrlPathPrefix string
	driver             string
	dataSource         string
}

type TestCases []*TestCase

type CheckFunc func(*testing.T, *httptest.Server, *TestCase)

var (
	defaultTestCases = TestCases{
		NewTestCase(true, "", ANY_DB_DRIVER),
	}

	ruleCreationTestCases = TestCases{
		NewTestCase(true, "", ANY_DB_DRIVER),
		NewTestCase(false, "/goslow", ANY_DB_DRIVER),
		NewTestCase(false, "/goslow/", ANY_DB_DRIVER),
		NewTestCase(false, "/te", ANY_DB_DRIVER),
		NewTestCase(false, "/te/", ANY_DB_DRIVER),
		NewTestCase(false, "/composite/path", ANY_DB_DRIVER),
	}
)

func NewTestCase(createDefaultRules bool, adminUrlPathPrefix string, driver string) *TestCase {
	dataSource, knownDriver := DATA_SOURCE[driver]
	if driver != ANY_DB_DRIVER && !knownDriver {
		log.Fatalf("unknown driver: <%s>", driver)
	}
	return &TestCase{
		createDefaultRules: createDefaultRules,
		adminUrlPathPrefix: adminUrlPathPrefix,
		driver:             driver,
		dataSource:         dataSource,
	}
}

func TestZeroSite(t *testing.T) {
	runAll(t, checkZeroSite, defaultTestCases)
}

func runAll(t *testing.T, checkFunc CheckFunc, testCases TestCases) {
	for _, testCase := range runnable(all(testCases)) {
		run(t, checkFunc, testCase)
	}
}

func runnable(testCases TestCases) TestCases {
	runnableTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if !testCase.skippable() {
			runnableTestCases = append(runnableTestCases, testCase)
		}
	}
	return runnableTestCases
}

func all(testCases TestCases) TestCases {
	allTestCases := make(TestCases, 0)
	for _, testCase := range testCases {
		if testCase.driver == ANY_DB_DRIVER {
			sqlite3TestCase := NewTestCase(testCase.createDefaultRules, testCase.adminUrlPathPrefix, "sqlite3")
			postgresTestCase := NewTestCase(testCase.createDefaultRules, testCase.adminUrlPathPrefix, "postgres")
			allTestCases = append(allTestCases, sqlite3TestCase)
			allTestCases = append(allTestCases, postgresTestCase)
		} else {
			allTestCases = append(allTestCases, testCase)
		}
	}
	return allTestCases
}

func (testCase *TestCase) skippable() bool {
	return testCase.driver == "postgres" && testing.Short()
}

func run(t *testing.T, checkFunc CheckFunc, testCase *TestCase) {
	if testCase.driver == "postgres" {
		createDb(TEST_DB)
		defer dropDb(TEST_DB)
	}
	goSlowServer := newSubDomainServer(testCase)
	server := httptest.NewServer(goSlowServer)
	defer server.Close()
	defer goSlowServer.storage.db.Close()
	checkFunc(t, server, testCase)
}

func checkZeroSite(t *testing.T, server *httptest.Server, testCase *TestCase) {
	bytesShouldBeEqual(t,
		readBody(GET(server.URL, "/", makeHost("0", TEST_ENDPOINT))),
		DEFAULT_BODY)
}

func TestTooLargeDelay(t *testing.T) {
	runAll(t, checkTooLargeDelay, defaultTestCases)
}

func checkTooLargeDelay(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminUrlPathPrefix
	domain := newDomain(server, join(prefix, "/booya"), []byte("haha"))
	site := getSite(domain)
	resp := addRule(server, &Rule{Site: site, Delay: time.Duration(1000) * time.Second})
	shouldHaveStatusCode(t, http.StatusBadRequest, resp)
	resp = GET(server.URL, "/", domain)
	shouldHaveStatusCode(t, http.StatusNotFound, resp)
}

func TestRedefineBuiltinSites(t *testing.T) {
	runAll(t, checkRedefineBuiltinSites, defaultTestCases)
}

func checkRedefineBuiltinSites(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, site := range []string{"0", "99", "500", "create"} {
		resp := addRule(server, &Rule{Site: site, Path: "/test", Body: []byte("hop"), Method: "GET"})
		shouldHaveStatusCode(t, http.StatusForbidden, resp)
	}
}

func TestRedefineNonExistentSite(t *testing.T) {
	runAll(t, checkRedefineNonExistentSite, defaultTestCases)
}

func checkRedefineNonExistentSite(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, site := range []string{"", "ha", "admin-500"} {
		resp := addRule(server, &Rule{Site: site})
		shouldHaveStatusCode(t, http.StatusNotFound, resp)
	}
}

func TestDelay(t *testing.T) {
	runAll(t, checkDelay, defaultTestCases)
}

func checkDelay(t *testing.T, server *httptest.Server, testCase *TestCase) {
	shouldRespondIn(t,
		createGET(server.URL, "/", makeHost("0", TEST_ENDPOINT)),
		0, 0.1) // seconds
	shouldRespondIn(t,
		createGET(server.URL, "/", makeHost("1", TEST_ENDPOINT)),
		1, 1.1) // seconds
}

func TestStatus(t *testing.T) {
	runAll(t, checkStatus, defaultTestCases)
}

func checkStatus(t *testing.T, server *httptest.Server, testCase *TestCase) {
	for _, statusCode := range []int{200, 404, 500} {
		resp := GET(server.URL, "/", makeHost(strconv.Itoa(statusCode), TEST_ENDPOINT))
		shouldHaveStatusCode(t, statusCode, resp)
	}
}

func TestRuleCreation(t *testing.T) {
	runAll(t, checkRuleCreationTestCase, ruleCreationTestCases)
}

func checkRuleCreationTestCase(t *testing.T, server *httptest.Server, testCase *TestCase) {
	prefix := testCase.adminUrlPathPrefix
	isInSingleSiteMode := prefix != ""
	domain, site := TEST_ENDPOINT, ""
	root_body := []byte("haha")
	test_body := []byte("hop")
	test_post_body := []byte("for POST")
	empty_payload := []byte("")

	if isInSingleSiteMode {
		resp := addRule(server, &Rule{Path: join(prefix, "/"), Body: root_body})
		shouldHaveStatusCode(t, http.StatusOK, resp)
	} else {
		domain = newDomain(server, join(prefix, "/"), root_body)
		site = getSite(domain)
	}

	bytesShouldBeEqual(t, readBody(GET(server.URL, "/", domain)), root_body)

	// testing GET rule
	resp := addRule(server, &Rule{Site: site, Path: join(prefix, "/test"), Body: test_body, Method: "GET"})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that GET /test rule works
	bytesShouldBeEqual(t, readBody(GET(server.URL, "/test", domain)), test_body)
	// checking that GET /test doesn't affect POST
	resp = POST(server.URL, "/test", domain, []byte(""))
	intShouldBeEqual(t, 404, resp.StatusCode)

	// testing POST rule
	resp = addRule(server, &Rule{Site: site, Path: join(prefix, "/test"), Body: test_post_body, Method: "POST",
		Delay: time.Duration(100) * time.Millisecond})
	shouldHaveStatusCode(t, http.StatusOK, resp)
	// checking that POST rule doesn't affect GET
	bytesShouldBeEqual(t, readBody(GET(server.URL, "/test", domain)), test_body)
	// checking that POST /test rule works
	bytesShouldBeEqual(t, readBody(POST(server.URL, "/test", domain, empty_payload)), test_post_body)
	shouldRespondIn(t, createPOST(server.URL, "/test", domain, empty_payload), 0.1, 0.15)
}

func newSubDomainServer(testCase *TestCase) *Server {
	config := DEFAULT_CONFIG // copies DEFAULT_CONFIG
	config.endpoint = TEST_ENDPOINT
	config.createDefaultRules = testCase.createDefaultRules
	config.adminUrlPathPrefix = testCase.adminUrlPathPrefix
	config.driver = testCase.driver
	config.dataSource = testCase.dataSource
	return NewServer(&config)
}

func GET(url, path, host string) *http.Response {
	req := createGET(url, path, host)
	return do(req)
}

func do(req *http.Request) *http.Response {
	resp, err := new(http.Client).Do(req)
	if err != nil {
		log.Fatal(err)
	}
	return resp
}

func createGET(url, path, host string) *http.Request {
	return createRequest("GET", url, path, host, nil)
}

func createRequest(method, url, path, host string, body io.Reader) *http.Request {
	req, err := http.NewRequest(method, url+path, body)
	if err != nil {
		log.Fatal(err)
	}
	req.Host = host
	return req
}

func readBody(resp *http.Response) []byte {
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	return body
}

func makeHost(subdomain, host string) string {
	return fmt.Sprintf("%s.%s", subdomain, host)
}

func bytesShouldBeEqual(t *testing.T, expected, actual []byte) {
	if !bytes.Equal(expected, actual) {
		t.Fatalf("<<%v>> != <<%v>>", string(expected), string(actual))
	}
}

func intShouldBeEqual(t *testing.T, expected, actual int) {
	if expected != actual {
		t.Fatalf("<<%v>> != <<%v>>", expected, actual)
	}
}

func shouldRespondIn(t *testing.T, req *http.Request, minSeconds, maxSeconds float64) {
	start := time.Now()
	resp := do(req)
	readBody(resp)
	duration := time.Since(start)
	minDuration := toDuration(minSeconds)
	maxDuration := toDuration(maxSeconds)
	if duration < minDuration || duration > maxDuration {
		t.Fatalf("%s%s answered in %v. Not in the interval [%v; %v]",
			req.Host, req.URL.Path, duration, minDuration, maxDuration)
	}
}

func shouldHaveStatusCode(t *testing.T, statusCode int, resp *http.Response) {
	intShouldBeEqual(t, statusCode, resp.StatusCode)
}

func toDuration(seconds float64) time.Duration {
	return time.Duration(seconds*1000) * time.Millisecond
}

func newDomain(server *httptest.Server, path string, response []byte) string {
	resp := POST(server.URL, fmt.Sprintf("%s?output=short&method=GET", path),
		makeHost("create", TEST_ENDPOINT), response)
	return string(readBody(resp))
}

func POST(url, path, host string, payload []byte) *http.Response {
	req := createPOST(url, path, host, payload)
	return do(req)
}

func createPOST(url, path, host string, payload []byte) *http.Request {
	return createRequest("POST", url, path, host, bytes.NewReader(payload))
}

func getSite(domain string) string {
	return strings.Split(domain, ".")[0]
}

func addRule(server *httptest.Server, rule *Rule) *http.Response {
	req := createPOST(server.URL, rule.Path, makeHost("admin-"+rule.Site, TEST_ENDPOINT),
		rule.Body)
	req.URL.RawQuery = getQueryString(rule)
	return do(req)
}

func getQueryString(rule *Rule) string {
	params := url.Values{}
	params.Set("method", rule.Method)
	params.Set("delay", fmt.Sprintf("%f", rule.Delay.Seconds()))
	return params.Encode()
}

// Wrapper around path.Join. Preserves trailing slash.
func join(elem ...string) string {
	lastElem := elem[len(elem)-1]
	shouldEndWithSlash := strings.HasSuffix(lastElem, "/")
	joined := path.Join(elem...)
	if shouldEndWithSlash && !strings.HasSuffix(joined, "/") {
		joined += "/"
	}
	return joined
}

func createDb(name string) {
	cmd := exec.Command("createdb", name)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("createdb error: %s", err)
	}
}

func dropDb(name string) {
	cmd := exec.Command("dropdb", name)
	err := cmd.Run()
	if err != nil {
		log.Fatalf("dropdb error: %s", err)
	}
}
<|endoftext|>


<commit_before>// Copyright (c) - Damien Fontaine <damien.fontaine@lineolia.net>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>

package lunarc

import (
	"net"
	"net/http"
	"testing"
	"time"
)

func TestInitialize(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	if server.conf.Port != 8888 {
		t.Fatalf("Non expected server port: %v != %v", 8888, server.conf.Port)
	}
	if server.conf.Jwt.Key != "LunarcSecretKey" {
		t.Fatalf("Non expected server Jwt secret key: %v != %v", "LunarcSecretKey", server.conf.Port)
	}
}

func TestStart(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	go server.Start()

	time.Sleep(time.Second * 3)

	_, err = http.Get("http://localhost:8888/")
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	go server.Stop()
	<-server.Done
}

func TestStartWithError(t *testing.T) {
	l, err := net.Listen("tcp", ":8888")
	defer l.Close()
	go http.Serve(l, nil)

	server, _ := NewWebServer("config.yml", "test")

	go server.Start()

	err = <-server.Error

	if err == nil {
		t.Fatalf("Expected error: listen tcp :8888: bind: address already in use")
	}
}

func TestStopNormal(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	go server.Start()

	time.Sleep(time.Second * 3)

	_, err = http.Get("http://localhost:8888/")
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	go server.Stop()
	<-server.Done

	resp, err := http.Get("http://localhost:8888/")
	if err == nil {
		t.Fatalf("Error expected: Not Found: %v", resp)
	}
}

func TestStopUnstarted(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}

	go server.Stop()

	select {
	case <-server.Done:
		t.Fatalf("Non expected behavior")
	case err := <-server.Error:
		if err == nil {
			t.Fatalf("Non expected error")
		}
		return
	}
}
<commit_msg>Add error message in server_test.go<commit_after>// Copyright (c) - Damien Fontaine <damien.fontaine@lineolia.net>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>

package lunarc

import (
	"net"
	"net/http"
	"testing"
	"time"
)

func TestNewWebServer(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	if server.conf.Port != 8888 {
		t.Fatalf("Non expected server port: %v != %v", 8888, server.conf.Port)
	}
	if server.conf.Jwt.Key != "LunarcSecretKey" {
		t.Fatalf("Non expected server Jwt secret key: %v != %v", "LunarcSecretKey", server.conf.Port)
	}
}

func TestStart(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	go server.Start()

	time.Sleep(time.Second * 3)

	_, err = http.Get("http://localhost:8888/")
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	go server.Stop()
	<-server.Done
}

func TestStartWithError(t *testing.T) {
	l, err := net.Listen("tcp", ":8888")
	if err != nil {
		t.Fatalf("Error during test preparation : %v", err)
	}
	defer l.Close()
	go http.Serve(l, nil)

	server, _ := NewWebServer("config.yml", "test")

	go server.Start()

	err = <-server.Error

	if err == nil {
		t.Fatalf("Expected error: listen tcp :8888: bind: address already in use")
	}
}

func TestStopNormal(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}
	go server.Start()

	time.Sleep(time.Second * 3)

	_, err = http.Get("http://localhost:8888/")
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	go server.Stop()
	<-server.Done

	resp, err := http.Get("http://localhost:8888/")
	if err == nil {
		t.Fatalf("Error expected: Not Found: %v", resp)
	}
}

func TestStopUnstarted(t *testing.T) {
	server, err := NewWebServer("config.yml", "test")
	if err != nil {
		t.Fatalf("Non expected error: %v", err)
	}

	go server.Stop()

	select {
	case <-server.Done:
		t.Fatalf("Non expected behavior")
	case err := <-server.Error:
		if err == nil {
			t.Fatalf("Non expected error")
		}
		return
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"os"
	"sort"
	"strings"
)

var exitCode int

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		doDir(".")
	} else {
		for _, name := range flag.Args() {
			// Is it a directory?
			if fi, err := os.Stat(name); err == nil && fi.IsDir() {
				doDir(name)
			} else {
				errorf("not a directory: %s", name)
			}
		}
	}
	os.Exit(exitCode)
}

// error formats the error to standard error, adding program
// identification and a newline
func errorf(format string, args ...interface{}) {
	fmt.Fprintf(os.Stderr, "deadcode: "+format+"\n", args...)
	exitCode = 2
}

func doDir(name string) {
	notests := func(info os.FileInfo) bool {
		if !info.IsDir() && strings.HasSuffix(info.Name(), ".go") &&
			!strings.HasSuffix(info.Name(), "_test.go") {
			return true
		}
		return false
	}
	fs := token.NewFileSet()
	pkgs, err := parser.ParseDir(fs, name, notests, parser.Mode(0))
	if err != nil {
		errorf("%s", err)
		return
	}
	for _, pkg := range pkgs {
		doPackage(fs, pkg)
	}
}

type Package struct {
	p    *ast.Package
	fs   *token.FileSet
	decl map[string]ast.Node
	used map[string]bool
}

func doPackage(fs *token.FileSet, pkg *ast.Package) {
	p := &Package{
		p:    pkg,
		fs:   fs,
		decl: make(map[string]ast.Node),
		used: make(map[string]bool),
	}
	for _, file := range pkg.Files {
		for _, decl := range file.Decls {
			switch n := decl.(type) {
			case *ast.GenDecl:
				// var, const, types
				for _, spec := range n.Specs {
					switch s := spec.(type) {
					case *ast.ValueSpec:
						// constants and variables.
						for _, name := range s.Names {
							p.decl[name.Name] = n
						}
					case *ast.TypeSpec:
						// type definitions.
						p.decl[s.Name.Name] = n
					}
				}
			case *ast.FuncDecl:
				// function declarations
				// TODO(remy): do methods
				if n.Recv == nil {
					p.decl[n.Name.Name] = n
				}
			}
		}
	}
	// init() and _ are always used
	p.used["init"] = true
	p.used["_"] = true
	if pkg.Name != "main" {
		// exported names are marked used for non-main packages.
		for name := range p.decl {
			if ast.IsExported(name) {
				p.used[name] = true
			}
		}
	} else {
		// in main programs, main() is called.
		p.used["main"] = true
	}
	for _, file := range pkg.Files {
		// walk file looking for used nodes.
		ast.Walk(p, file)
	}
	// reports.
	var reports Reports
	for name, node := range p.decl {
		if !p.used[name] {
			pos := node.Pos()
			if node, ok := node.(*ast.GenDecl); ok && node.Lparen.IsValid() {
				for _, spec := range node.Specs {
					switch spec := spec.(type) {
					case *ast.ValueSpec:
						for _, s := range spec.Names {
							if s.Name == name {
								pos = s.NamePos
								break
							}
						}
					case *ast.TypeSpec:
						pos = spec.Name.Pos()
					}
				}
			}
			reports = append(reports, Report{pos, name})
		}
	}
	sort.Sort(reports)
	for _, report := range reports {
		fmt.Printf("%s: %s is unused\n", fset.Position(report.pos), report.name)
	}
}

type Report struct {
	pos  token.Pos
	name string
}
type Reports []Report

func (l Reports) Len() int           { return len(l) }
func (l Reports) Less(i, j int) bool { return l[i].pos < l[j].pos }
func (l Reports) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

// Visits files for used nodes.
func (p *Package) Visit(node ast.Node) ast.Visitor {
	u := usedWalker(*p) // hopefully p fields are references.
	switch n := node.(type) {
	// don't walk whole file, but only:
	case *ast.ValueSpec:
		// - variable initializers
		for _, value := range n.Values {
			ast.Walk(&u, value)
		}
		// variable types.
		if n.Type != nil {
			ast.Walk(&u, n.Type)
		}
	case *ast.BlockStmt:
		// - function bodies
		for _, stmt := range n.List {
			ast.Walk(&u, stmt)
		}
	case *ast.FuncDecl:
		// - function signatures
		ast.Walk(&u, n.Type)
	case *ast.TypeSpec:
		// - type declarations
		ast.Walk(&u, n.Type)
	}
	return p
}

type usedWalker Package

// Walks through the AST marking used identifiers.
func (p *usedWalker) Visit(node ast.Node) ast.Visitor {
	// just be stupid and mark all *ast.Ident
	switch n := node.(type) {
	case *ast.Ident:
		p.used[n.Name] = true
	}
	return p
}
<commit_msg>Run on packages, not directories<commit_after>package main

import (
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/token"
	"log"
	"os"
	"sort"
	"strings"

	"github.com/kisielk/gotool"
)

var exitCode int

func main() {
	// FIXME check flag.NArgs
	paths := gotool.ImportPaths([]string{os.Args[1]})
	cwd, err := os.Getwd()
	if err != nil {
		// XXX
		log.Fatal(err)
	}
	for _, path := range paths {
		pkg, err := build.Import(path, cwd, build.FindOnly)
		if err != nil {
			// XXX
			log.Fatal(err)
		}
		fset := token.NewFileSet()
		pkgs, err := parser.ParseDir(fset, pkg.Dir, nil, 0)
		if err != nil {
			// XXX
			log.Fatal(err)
		}
		for _, pkg := range pkgs {
			doPackage(fset, pkg)
		}

	}
	os.Exit(exitCode)
}

type Package struct {
	p    *ast.Package
	fset *token.FileSet
	decl map[string]ast.Node
	used map[string]bool
}

func doPackage(fset *token.FileSet, pkg *ast.Package) {
	p := &Package{
		p:    pkg,
		fset: fset,
		decl: make(map[string]ast.Node),
		used: make(map[string]bool),
	}
	for _, file := range pkg.Files {
		for _, decl := range file.Decls {
			switch n := decl.(type) {
			case *ast.GenDecl:
				// var, const, types
				for _, spec := range n.Specs {
					switch s := spec.(type) {
					case *ast.ValueSpec:
						// constants and variables.
						for _, name := range s.Names {
							p.decl[name.Name] = n
						}
					case *ast.TypeSpec:
						// type definitions.
						p.decl[s.Name.Name] = n
					}
				}
			case *ast.FuncDecl:
				// function declarations
				// TODO(remy): do methods
				if n.Recv == nil {
					p.decl[n.Name.Name] = n
				}
			}
		}
	}
	// init() and _ are always used
	p.used["init"] = true
	p.used["_"] = true
	if pkg.Name != "main" {
		// exported names are marked used for non-main packages.
		for name := range p.decl {
			if ast.IsExported(name) {
				p.used[name] = true
			}
		}
	} else {
		// in main programs, main() is called.
		p.used["main"] = true
	}
	for _, file := range pkg.Files {
		// walk file looking for used nodes.
		ast.Walk(p, file)
	}
	// reports.
	var reports Reports
	for name, node := range p.decl {
		if !p.used[name] {
			pos := node.Pos()
			if node, ok := node.(*ast.GenDecl); ok && node.Lparen.IsValid() {
				for _, spec := range node.Specs {
					switch spec := spec.(type) {
					case *ast.ValueSpec:
						for _, s := range spec.Names {
							if s.Name == name {
								pos = s.NamePos
								break
							}
						}
					case *ast.TypeSpec:
						pos = spec.Name.Pos()
					}
				}
			}
			reports = append(reports, Report{pos, name})
		}
	}
	sort.Sort(reports)
	for _, report := range reports {
		fmt.Printf("%s: %s is unused\n", fset.Position(report.pos), report.name)
	}
}

type Report struct {
	pos  token.Pos
	name string
}
type Reports []Report

func (l Reports) Len() int           { return len(l) }
func (l Reports) Less(i, j int) bool { return l[i].pos < l[j].pos }
func (l Reports) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

// Visits files for used nodes.
func (p *Package) Visit(node ast.Node) ast.Visitor {
	u := usedWalker(*p) // hopefully p fields are references.
	switch n := node.(type) {
	// don't walk whole file, but only:
	case *ast.ValueSpec:
		// - variable initializers
		for _, value := range n.Values {
			ast.Walk(&u, value)
		}
		// variable types.
		if n.Type != nil {
			ast.Walk(&u, n.Type)
		}
	case *ast.BlockStmt:
		// - function bodies
		for _, stmt := range n.List {
			ast.Walk(&u, stmt)
		}
	case *ast.FuncDecl:
		// - function signatures
		ast.Walk(&u, n.Type)
	case *ast.TypeSpec:
		// - type declarations
		ast.Walk(&u, n.Type)
	}
	return p
}

type usedWalker Package

// Walks through the AST marking used identifiers.
func (p *usedWalker) Visit(node ast.Node) ast.Visitor {
	// just be stupid and mark all *ast.Ident
	switch n := node.(type) {
	case *ast.Ident:
		p.used[n.Name] = true
	}
	return p
}
<|endoftext|>


<commit_before>package service

import (
	"context"
	"database/sql"
	"time"

	"github.com/gilcrest/go-api-basic/datastore"
	"github.com/gilcrest/go-api-basic/datastore/appstore"
	"github.com/gilcrest/go-api-basic/domain/app"
	"github.com/gilcrest/go-api-basic/domain/audit"
	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/org"
	"github.com/gilcrest/go-api-basic/domain/secure"
	"github.com/gilcrest/go-api-basic/domain/user"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v4"
)

// CreateAppRequest is the request struct for Creating an App
type CreateAppRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

// AppResponse is the response struct for an App
type AppResponse struct {
	ExternalID  string           `json:"external_id"`
	Name        string           `json:"name"`
	Description string           `json:"description"`
	APIKeys     []APIKeyResponse `json:"api_keys"`
}

// APIKeyResponse is the response fields for an API key
type APIKeyResponse struct {
	Key              string `json:"key"`
	DeactivationDate string `json:"deactivation_date"`
}

// newAPIKeyResponse initializes an APIKeyResponse. The app.APIKey is
// decrypted and set to the Key field as part of initialization.
func newAPIKeyResponse(key app.APIKey) APIKeyResponse {
	return APIKeyResponse{Key: key.Key(), DeactivationDate: key.DeactivationDate().String()}
}

// newAppResponse initializes an AppResponse given an app.App
func newAppResponse(a app.App) AppResponse {
	var keys []APIKeyResponse
	for _, key := range a.APIKeys {
		akr := newAPIKeyResponse(key)
		keys = append(keys, akr)
	}
	return AppResponse{
		ExternalID:  a.ExternalID.String(),
		Name:        a.Name,
		Description: a.Description,
		APIKeys:     keys,
	}
}

// CreateAppService is a service for creating an App
type CreateAppService struct {
	Datastorer            Datastorer
	RandomStringGenerator CryptoRandomGenerator
	EncryptionKey         *[32]byte
}

// Create is used to create an App
func (s CreateAppService) Create(ctx context.Context, r *CreateAppRequest, adt audit.Audit) (AppResponse, error) {
	var (
		a   app.App
		err error
	)
	a.ID = uuid.New()
	a.ExternalID = secure.NewID()
	a.Org = adt.App.Org
	a.Name = r.Name
	a.Description = r.Description

	keyDeactivation := time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC)
	err = a.AddNewKey(s.RandomStringGenerator, s.EncryptionKey, keyDeactivation)
	if err != nil {
		return AppResponse{}, err
	}

	// start db txn using pgxpool
	tx, err := s.Datastorer.BeginTx(ctx)
	if err != nil {
		return AppResponse{}, err
	}

	err = createAppDB(ctx, s.Datastorer, tx, a, adt)
	if err != nil {
		return AppResponse{}, err
	}

	// commit db txn using pgxpool
	err = s.Datastorer.CommitTx(ctx, tx)
	if err != nil {
		return AppResponse{}, err
	}

	return newAppResponse(a), nil
}

// createAppDB creates an app in the database given a domain app.App and audit.Audit
func createAppDB(ctx context.Context, ds Datastorer, tx pgx.Tx, a app.App, adt audit.Audit) error {
	var err error

	if len(a.APIKeys) == 0 {
		return errs.E(errs.Internal, ds.RollbackTx(ctx, tx, errs.E("app must have at least one API key.")))
	}

	// create app database record using appstore
	_, err = appstore.New(tx).CreateApp(ctx, newCreateAppParams(a, adt))
	if err != nil {
		return errs.E(errs.Database, ds.RollbackTx(ctx, tx, err))
	}

	for _, aak := range a.APIKeys {
		// create app API key database record using appstore
		_, err = appstore.New(tx).CreateAppAPIKey(ctx, newCreateAppAPIKeyParams(a, aak, adt))
		if err != nil {
			return errs.E(errs.Database, ds.RollbackTx(ctx, tx, err))
		}
	}

	return nil
}

// newCreateAppParams maps an App to appstore.CreateAppParams
func newCreateAppParams(a app.App, adt audit.Audit) appstore.CreateAppParams {
	return appstore.CreateAppParams{
		AppID:           a.ID,
		OrgID:           a.Org.ID,
		AppExtlID:       a.ExternalID.String(),
		AppName:         a.Name,
		AppDescription:  a.Description,
		Active:          sql.NullBool{Bool: true, Valid: true},
		CreateAppID:     adt.App.ID,
		CreateUserID:    datastore.NewNullUUID(adt.User.ID),
		CreateTimestamp: adt.Moment,
		UpdateAppID:     adt.App.ID,
		UpdateUserID:    datastore.NewNullUUID(adt.User.ID),
		UpdateTimestamp: adt.Moment,
	}
}

// newCreateAppAPIKeyParams maps an AppAPIKey to appstore.CreateAppAPIKeyParams
func newCreateAppAPIKeyParams(a app.App, k app.APIKey, adt audit.Audit) appstore.CreateAppAPIKeyParams {
	return appstore.CreateAppAPIKeyParams{
		ApiKey:          k.Ciphertext(),
		AppID:           a.ID,
		DeactvDate:      k.DeactivationDate(),
		CreateAppID:     adt.App.ID,
		CreateUserID:    datastore.NewNullUUID(adt.User.ID),
		CreateTimestamp: adt.Moment,
		UpdateAppID:     adt.App.ID,
		UpdateUserID:    datastore.NewNullUUID(adt.User.ID),
		UpdateTimestamp: adt.Moment,
	}
}

// FindAppService is a service for retrieving an App from the datastore
type FindAppService struct {
	Datastorer    Datastorer
	EncryptionKey *[32]byte
}

// FindAppByAPIKey finds an app given its External ID and determines
// if the given API key is a valid key for it. It is used as part of
// app authentication
func (fas FindAppService) FindAppByAPIKey(ctx context.Context, realm, appExtlID, apiKey string) (app.App, error) {

	var (
		kr  []appstore.FindAppAPIKeysByAppExtlIDRow
		err error
	)

	kr, err = appstore.New(fas.Datastorer.Pool()).FindAppAPIKeysByAppExtlID(ctx, appExtlID)
	if err != nil {
		return app.App{}, errs.E(errs.Unauthenticated, errs.Realm(realm), err)
	}

	var (
		a    app.App
		keys []app.APIKey
	)
	for i, row := range kr {
		if i == 0 { // only need to fill the app struct on first iteration
			var extl secure.Identifier
			extl, err = secure.ParseIdentifier(row.OrgExtlID)
			if err != nil {
				return app.App{}, err
			}
			a.ID = row.AppID
			a.ExternalID = extl
			a.Org = org.Org{
				ID:          row.OrgID,
				ExternalID:  extl,
				Name:        row.OrgName,
				Description: row.OrgDescription,
			}
			a.Name = row.AppName
			a.Description = row.AppDescription
		}
		var key app.APIKey
		key, err = app.NewAPIKeyFromCipher(row.ApiKey, fas.EncryptionKey)
		if err != nil {
			return app.App{}, err
		}
		key.SetDeactivationDate(row.DeactvDate)
		keys = append(keys, key)
	}
	a.APIKeys = keys

	err = a.ValidKey(realm, apiKey)
	if err != nil {
		return app.App{}, err
	}

	return a, nil
}

// findAppByID finds an app from the datastore given its ID
func findAppByID(ctx context.Context, dbtx DBTX, id uuid.UUID, withAudit bool) (app.App, *audit.SimpleAudit, error) {
	dba, err := appstore.New(dbtx).FindAppByID(ctx, id)
	if err != nil {
		return app.App{}, nil, errs.E(errs.Database, err)
	}

	a, sa, err := newApp(ctx, dbtx, dba, withAudit)
	if err != nil {
		return app.App{}, nil, err
	}

	return a, sa, nil
}

// newApp hydrates an appstore.App into an app.App and an audit.SimpleAudit (if withAudit is true)
func newApp(ctx context.Context, dbtx DBTX, dba appstore.App, withAudit bool) (app.App, *audit.SimpleAudit, error) {
	var (
		extl                   secure.Identifier
		err                    error
		createApp, updateApp   app.App
		createUser, updateUser user.User
	)

	o, _, err := findOrgByID(ctx, dbtx, dba.OrgID, false)
	if err != nil {
		return app.App{}, nil, err
	}

	extl, err = secure.ParseIdentifier(dba.AppExtlID)
	if err != nil {
		return app.App{}, nil, err
	}

	a := app.App{
		ID:          dba.AppID,
		ExternalID:  extl,
		Org:         o,
		Name:        dba.AppName,
		Description: dba.AppDescription,
		APIKeys:     nil,
	}

	sa := new(audit.SimpleAudit)
	if withAudit {
		createApp, _, err = findAppByID(ctx, dbtx, dba.CreateAppID, false)
		if err != nil {
			return app.App{}, nil, err
		}
		createUser, err = findUserByID(ctx, dbtx, dba.CreateUserID.UUID)
		if err != nil {
			return app.App{}, nil, err
		}
		createAudit := audit.Audit{
			App:    createApp,
			User:   createUser,
			Moment: dba.CreateTimestamp,
		}

		updateApp, _, err = findAppByID(ctx, dbtx, dba.UpdateAppID, false)
		if err != nil {
			return app.App{}, nil, err
		}
		updateUser, err = findUserByID(ctx, dbtx, dba.UpdateUserID.UUID)
		if err != nil {
			return app.App{}, nil, err
		}
		updateAudit := audit.Audit{
			App:    updateApp,
			User:   updateUser,
			Moment: dba.UpdateTimestamp,
		}

		sa = &audit.SimpleAudit{First: createAudit, Last: updateAudit}
	}

	return a, sa, nil
}
<commit_msg>changes for new findOrgByID function signature<commit_after>package service

import (
	"context"
	"database/sql"
	"time"

	"github.com/gilcrest/go-api-basic/datastore"
	"github.com/gilcrest/go-api-basic/datastore/appstore"
	"github.com/gilcrest/go-api-basic/domain/app"
	"github.com/gilcrest/go-api-basic/domain/audit"
	"github.com/gilcrest/go-api-basic/domain/errs"
	"github.com/gilcrest/go-api-basic/domain/org"
	"github.com/gilcrest/go-api-basic/domain/secure"
	"github.com/gilcrest/go-api-basic/domain/user"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v4"
)

// CreateAppRequest is the request struct for Creating an App
type CreateAppRequest struct {
	Name        string `json:"name"`
	Description string `json:"description"`
}

// AppResponse is the response struct for an App
type AppResponse struct {
	ExternalID  string           `json:"external_id"`
	Name        string           `json:"name"`
	Description string           `json:"description"`
	APIKeys     []APIKeyResponse `json:"api_keys"`
}

// APIKeyResponse is the response fields for an API key
type APIKeyResponse struct {
	Key              string `json:"key"`
	DeactivationDate string `json:"deactivation_date"`
}

// newAPIKeyResponse initializes an APIKeyResponse. The app.APIKey is
// decrypted and set to the Key field as part of initialization.
func newAPIKeyResponse(key app.APIKey) APIKeyResponse {
	return APIKeyResponse{Key: key.Key(), DeactivationDate: key.DeactivationDate().String()}
}

// newAppResponse initializes an AppResponse given an app.App
func newAppResponse(a app.App) AppResponse {
	var keys []APIKeyResponse
	for _, key := range a.APIKeys {
		akr := newAPIKeyResponse(key)
		keys = append(keys, akr)
	}
	return AppResponse{
		ExternalID:  a.ExternalID.String(),
		Name:        a.Name,
		Description: a.Description,
		APIKeys:     keys,
	}
}

// CreateAppService is a service for creating an App
type CreateAppService struct {
	Datastorer            Datastorer
	RandomStringGenerator CryptoRandomGenerator
	EncryptionKey         *[32]byte
}

// Create is used to create an App
func (s CreateAppService) Create(ctx context.Context, r *CreateAppRequest, adt audit.Audit) (AppResponse, error) {
	var (
		a   app.App
		err error
	)
	a.ID = uuid.New()
	a.ExternalID = secure.NewID()
	a.Org = adt.App.Org
	a.Name = r.Name
	a.Description = r.Description

	keyDeactivation := time.Date(2099, 12, 31, 0, 0, 0, 0, time.UTC)
	err = a.AddNewKey(s.RandomStringGenerator, s.EncryptionKey, keyDeactivation)
	if err != nil {
		return AppResponse{}, err
	}

	// start db txn using pgxpool
	tx, err := s.Datastorer.BeginTx(ctx)
	if err != nil {
		return AppResponse{}, err
	}

	err = createAppDB(ctx, s.Datastorer, tx, a, adt)
	if err != nil {
		return AppResponse{}, err
	}

	// commit db txn using pgxpool
	err = s.Datastorer.CommitTx(ctx, tx)
	if err != nil {
		return AppResponse{}, err
	}

	return newAppResponse(a), nil
}

// createAppDB creates an app in the database given a domain app.App and audit.Audit
func createAppDB(ctx context.Context, ds Datastorer, tx pgx.Tx, a app.App, adt audit.Audit) error {
	var err error

	if len(a.APIKeys) == 0 {
		return errs.E(errs.Internal, ds.RollbackTx(ctx, tx, errs.E("app must have at least one API key.")))
	}

	// create app database record using appstore
	_, err = appstore.New(tx).CreateApp(ctx, newCreateAppParams(a, adt))
	if err != nil {
		return errs.E(errs.Database, ds.RollbackTx(ctx, tx, err))
	}

	for _, aak := range a.APIKeys {
		// create app API key database record using appstore
		_, err = appstore.New(tx).CreateAppAPIKey(ctx, newCreateAppAPIKeyParams(a, aak, adt))
		if err != nil {
			return errs.E(errs.Database, ds.RollbackTx(ctx, tx, err))
		}
	}

	return nil
}

// newCreateAppParams maps an App to appstore.CreateAppParams
func newCreateAppParams(a app.App, adt audit.Audit) appstore.CreateAppParams {
	return appstore.CreateAppParams{
		AppID:           a.ID,
		OrgID:           a.Org.ID,
		AppExtlID:       a.ExternalID.String(),
		AppName:         a.Name,
		AppDescription:  a.Description,
		Active:          sql.NullBool{Bool: true, Valid: true},
		CreateAppID:     adt.App.ID,
		CreateUserID:    datastore.NewNullUUID(adt.User.ID),
		CreateTimestamp: adt.Moment,
		UpdateAppID:     adt.App.ID,
		UpdateUserID:    datastore.NewNullUUID(adt.User.ID),
		UpdateTimestamp: adt.Moment,
	}
}

// newCreateAppAPIKeyParams maps an AppAPIKey to appstore.CreateAppAPIKeyParams
func newCreateAppAPIKeyParams(a app.App, k app.APIKey, adt audit.Audit) appstore.CreateAppAPIKeyParams {
	return appstore.CreateAppAPIKeyParams{
		ApiKey:          k.Ciphertext(),
		AppID:           a.ID,
		DeactvDate:      k.DeactivationDate(),
		CreateAppID:     adt.App.ID,
		CreateUserID:    datastore.NewNullUUID(adt.User.ID),
		CreateTimestamp: adt.Moment,
		UpdateAppID:     adt.App.ID,
		UpdateUserID:    datastore.NewNullUUID(adt.User.ID),
		UpdateTimestamp: adt.Moment,
	}
}

// FindAppService is a service for retrieving an App from the datastore
type FindAppService struct {
	Datastorer    Datastorer
	EncryptionKey *[32]byte
}

// FindAppByAPIKey finds an app given its External ID and determines
// if the given API key is a valid key for it. It is used as part of
// app authentication
func (fas FindAppService) FindAppByAPIKey(ctx context.Context, realm, appExtlID, apiKey string) (app.App, error) {

	var (
		kr  []appstore.FindAppAPIKeysByAppExtlIDRow
		err error
	)

	kr, err = appstore.New(fas.Datastorer.Pool()).FindAppAPIKeysByAppExtlID(ctx, appExtlID)
	if err != nil {
		return app.App{}, errs.E(errs.Unauthenticated, errs.Realm(realm), err)
	}

	var (
		a    app.App
		keys []app.APIKey
	)
	for i, row := range kr {
		if i == 0 { // only need to fill the app struct on first iteration
			var extl secure.Identifier
			extl, err = secure.ParseIdentifier(row.OrgExtlID)
			if err != nil {
				return app.App{}, err
			}
			a.ID = row.AppID
			a.ExternalID = extl
			a.Org = org.Org{
				ID:          row.OrgID,
				ExternalID:  extl,
				Name:        row.OrgName,
				Description: row.OrgDescription,
			}
			a.Name = row.AppName
			a.Description = row.AppDescription
		}
		var key app.APIKey
		key, err = app.NewAPIKeyFromCipher(row.ApiKey, fas.EncryptionKey)
		if err != nil {
			return app.App{}, err
		}
		key.SetDeactivationDate(row.DeactvDate)
		keys = append(keys, key)
	}
	a.APIKeys = keys

	err = a.ValidKey(realm, apiKey)
	if err != nil {
		return app.App{}, err
	}

	return a, nil
}

// findAppByID finds an app from the datastore given its ID
func findAppByID(ctx context.Context, dbtx DBTX, id uuid.UUID, withAudit bool) (app.App, *audit.SimpleAudit, error) {
	dba, err := appstore.New(dbtx).FindAppByID(ctx, id)
	if err != nil {
		return app.App{}, nil, errs.E(errs.Database, err)
	}

	a, sa, err := newApp(ctx, dbtx, dba, withAudit)
	if err != nil {
		return app.App{}, nil, err
	}

	return a, sa, nil
}

// newApp hydrates an appstore.App into an app.App and an audit.SimpleAudit (if withAudit is true)
func newApp(ctx context.Context, dbtx DBTX, dba appstore.App, withAudit bool) (app.App, *audit.SimpleAudit, error) {
	var (
		extl                   secure.Identifier
		err                    error
		createApp, updateApp   app.App
		createUser, updateUser user.User
		o                      org.Org
	)

	o, err = findOrgByID(ctx, dbtx, dba.OrgID)
	if err != nil {
		return app.App{}, nil, err
	}

	extl, err = secure.ParseIdentifier(dba.AppExtlID)
	if err != nil {
		return app.App{}, nil, err
	}

	a := app.App{
		ID:          dba.AppID,
		ExternalID:  extl,
		Org:         o,
		Name:        dba.AppName,
		Description: dba.AppDescription,
		APIKeys:     nil,
	}

	sa := new(audit.SimpleAudit)
	if withAudit {
		createApp, _, err = findAppByID(ctx, dbtx, dba.CreateAppID, false)
		if err != nil {
			return app.App{}, nil, err
		}
		createUser, err = findUserByID(ctx, dbtx, dba.CreateUserID.UUID)
		if err != nil {
			return app.App{}, nil, err
		}
		createAudit := audit.Audit{
			App:    createApp,
			User:   createUser,
			Moment: dba.CreateTimestamp,
		}

		updateApp, _, err = findAppByID(ctx, dbtx, dba.UpdateAppID, false)
		if err != nil {
			return app.App{}, nil, err
		}
		updateUser, err = findUserByID(ctx, dbtx, dba.UpdateUserID.UUID)
		if err != nil {
			return app.App{}, nil, err
		}
		updateAudit := audit.Audit{
			App:    updateApp,
			User:   updateUser,
			Moment: dba.UpdateTimestamp,
		}

		sa = &audit.SimpleAudit{First: createAudit, Last: updateAudit}
	}

	return a, sa, nil
}
<|endoftext|>


<commit_before>//
package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mitchellh/go-homedir"
)

// main
func main() {

	var download string

	// accept a flag allowing for alternate download options
	flag.StringVar(&download, "o", "nanobox", "The version of nanobox to update")
	flag.Parse()

	// if download is not one of our available download options default to "nanobox"
	if _, ok := map[string]int{"nanobox": 1, "nanobox-dev": 1}[download]; !ok {
		fmt.Printf("'%s' is not a valid option. Downloading 'nanobox'\n", download)
		download = "nanobox"
	}

	// before attempting to update, ensure nanobox is installed (on the path)
	path, err := exec.LookPath(download)
	if err != nil {
		fmt.Printf("Unable to update '%s' (not found on path)\n", download)
		os.Exit(1)
	}

	fmt.Printf("Updating %s...\n", download)

	// set Home based off the users homedir (~)
	home, err := homedir.Dir()
	if err != nil {
		fmt.Println("Unable to determine home directory - ", err.Error())
		os.Exit(1)
	}

	tmpDir := filepath.Join(home, ".nanobox", "tmp")
	tmpPath := filepath.Join(tmpDir, download)

	// if tmp dir doesn't exist fail. The updater shouldn't run if nanobox has never
	// been run.
	if _, err := os.Stat(tmpDir); err != nil {
		fmt.Println("Nanobox updater required nanobox be configured (run once) before it can update.", err.Error())
		os.Exit(1)
	}

	// create a tmp CLI in tmp dir
	tmpFile, err := os.Create(tmpPath)
	if err != nil {
		fmt.Println("Failed to create temporary file - ", err.Error())
		os.Exit(1)
	}
	defer tmpFile.Close()

	// download the new CLI
	progress(fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/%v", runtime.GOOS, runtime.GOARCH, download), tmpFile)

	// make new CLI executable
	if err := os.Chmod(tmpPath, 0755); err != nil {
		fmt.Println("Failed to set permissions - ", err.Error())
	}

	// ensure new CLI download matches the remote md5; if the download fails for any
	// reason these md5's should NOT match.
	if _, err = md5sMatch(tmpPath, fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/%v.md5", runtime.GOOS, runtime.GOARCH, download)); err != nil {
		fmt.Printf("Nanobox was unable to correctly download the update. Please check your internet connection and try again.")
		os.Exit(1)
	}

	// replace the old CLI with the new one
	if err = os.Rename(tmpPath, path); err != nil {
		fmt.Println("Failed to replace existing CLI with new one -", err.Error())
		os.Exit(1)
	}

	// execute the new CLI printing the version to verify update;  if the new CLI
	out, err := exec.Command(path, "-v").Output()

	// fails to execute, just print a generic message and return
	if err != nil {
		fmt.Printf("[√] Update successful")
		return
	}

	fmt.Printf("[√] Now running %s", string(out))
}

// progress downloads a file with a fancy progress bar
func progress(path string, w io.Writer) error {

	//
	download, err := http.Get(path)
	if err != nil {
		return err
	}
	defer download.Body.Close()

	var percent float64
	var down int

	// format the response content length to be more 'friendly'
	total := float64(download.ContentLength) / math.Pow(1024, 2)

	// create a 'buffer' to read into
	p := make([]byte, 2048)

	//
	for {

		// read the response body (streaming)
		n, err := download.Body.Read(p)

		// write to our buffer
		w.Write(p[:n])

		// update the total bytes read
		down += n

		switch {
		default:
			// update the percent downloaded
			percent = (float64(down) / float64(download.ContentLength)) * 100

			// show download progress: 0.0/0.0MB [*** progress *** 0.0%]
			fmt.Printf("\r   %.2f/%.2fMB [%-41s %.2f%%]", float64(down)/math.Pow(1024, 2), total, strings.Repeat("*", int(percent/2.5)), percent)

		// if no more files are found return
		case download.ContentLength < 1:
			fmt.Printf("\r   %.2fMB", float64(down)/math.Pow(1024, 2))
		}

		// detect EOF and break the 'stream'
		if err != nil {
			if err == io.EOF {
				fmt.Println("")
				break
			} else {
				return err
			}
		}
	}

	return nil
}

// md5sMatch determines if a local MD5 matches a remote MD5
func md5sMatch(localFile, remotePath string) (bool, error) {

	// read the local file; will return os.PathError if doesn't exist
	b, err := ioutil.ReadFile(localFile)
	if err != nil {
		return false, err
	}

	// get local md5 checksum (as a string)
	localMD5 := fmt.Sprintf("%x", md5.Sum(b))

	// GET remote md5
	res, err := http.Get(remotePath)
	if err != nil {
		return false, err
	}
	defer res.Body.Close()

	// read the remote md5 checksum
	remoteMD5, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return false, err
	}

	// compare checksum's
	return strings.TrimSpace(localMD5) == strings.TrimSpace(string(remoteMD5)), nil
}
<commit_msg>small update to some comments, and some minor code restructuring<commit_after>//
package main

import (
	"crypto/md5"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mitchellh/go-homedir"
)

// main
func main() {

	var download string

	// accept a flag allowing for alternate download options
	flag.StringVar(&download, "o", "nanobox", "The version of nanobox to update")
	flag.Parse()

	// if download is not one of our available download options default to "nanobox"
	if _, ok := map[string]int{"nanobox": 1, "nanobox-dev": 1}[download]; !ok {
		fmt.Printf("'%s' is not a valid option. Downloading 'nanobox'\n", download)
		download = "nanobox"
	}

	// before attempting to update, ensure nanobox is installed (on the path)
	path, err := exec.LookPath(download)
	if err != nil {
		fmt.Printf("Unable to update '%s' (not found on path)\n", download)
		os.Exit(1)
	}

	fmt.Printf("Updating %s...\n", download)

	// get the current users home dir
	home, err := homedir.Dir()
	if err != nil {
		fmt.Println("Unable to determine home directory - ", err.Error())
		os.Exit(1)
	}

	tmpDir := filepath.Join(home, ".nanobox", "tmp")
	tmpPath := filepath.Join(tmpDir, download)

	// if tmp dir doesn't exist fail. The updater shouldn't run if nanobox has never
	// been run.
	if _, err := os.Stat(tmpDir); err != nil {
		fmt.Println("Nanobox updater required nanobox be configured (run once) before it can update.", err.Error())
		os.Exit(1)
	}

	// create a tmp CLI in tmp dir
	tmpFile, err := os.Create(tmpPath)
	if err != nil {
		fmt.Println("Failed to create temporary file - ", err.Error())
		os.Exit(1)
	}
	defer tmpFile.Close()

	// download the new CLI
	progress(fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/%v", runtime.GOOS, runtime.GOARCH, download), tmpFile)

	// ensure new CLI download matches the remote md5; if the download fails for any
	// reason these md5's should NOT match.
	if _, err = md5sMatch(tmpPath, fmt.Sprintf("https://s3.amazonaws.com/tools.nanobox.io/cli/%v/%v/%v.md5", runtime.GOOS, runtime.GOARCH, download)); err != nil {
		fmt.Printf("Nanobox was unable to correctly download the update. Please check your internet connection and try again.")
		os.Exit(1)
	}

	// make new CLI executable
	if err := os.Chmod(tmpPath, 0755); err != nil {
		fmt.Println("Failed to set permissions - ", err.Error())
	}

	// replace the old CLI with the new one
	if err = os.Rename(tmpPath, path); err != nil {
		fmt.Println("Failed to replace existing CLI with new one -", err.Error())
		os.Exit(1)
	}

	// execute the new CLI printing the version to verify update
	out, err := exec.Command(path, "-v").Output()

	// if the new CLI fails to execute, just print a generic message and return
	if err != nil {
		fmt.Printf("[√] Update successful")
		return
	}

	fmt.Printf("[√] Now running %s", string(out))
}

// progress downloads a file with a fancy progress bar
func progress(path string, w io.Writer) error {

	//
	download, err := http.Get(path)
	if err != nil {
		return err
	}
	defer download.Body.Close()

	var percent float64
	var down int

	// format the response content length to be more 'friendly'
	total := float64(download.ContentLength) / math.Pow(1024, 2)

	// create a 'buffer' to read into
	p := make([]byte, 2048)

	//
	for {

		// read the response body (streaming)
		n, err := download.Body.Read(p)

		// write to our buffer
		w.Write(p[:n])

		// update the total bytes read
		down += n

		switch {
		default:
			// update the percent downloaded
			percent = (float64(down) / float64(download.ContentLength)) * 100

			// show download progress: 0.0/0.0MB [*** progress *** 0.0%]
			fmt.Printf("\r   %.2f/%.2fMB [%-41s %.2f%%]", float64(down)/math.Pow(1024, 2), total, strings.Repeat("*", int(percent/2.5)), percent)

		// if no more files are found return
		case download.ContentLength < 1:
			fmt.Printf("\r   %.2fMB", float64(down)/math.Pow(1024, 2))
		}

		// detect EOF and break the 'stream'
		if err != nil {
			if err == io.EOF {
				fmt.Println("")
				break
			} else {
				return err
			}
		}
	}

	return nil
}

// md5sMatch determines if a local MD5 matches a remote MD5
func md5sMatch(localFile, remotePath string) (bool, error) {

	// read the local file; will return os.PathError if doesn't exist
	b, err := ioutil.ReadFile(localFile)
	if err != nil {
		return false, err
	}

	// get local md5 checksum (as a string)
	localMD5 := fmt.Sprintf("%x", md5.Sum(b))

	// GET remote md5
	res, err := http.Get(remotePath)
	if err != nil {
		return false, err
	}
	defer res.Body.Close()

	// read the remote md5 checksum
	remoteMD5, err := ioutil.ReadAll(res.Body)
	if err != nil {
		return false, err
	}

	// compare checksum's
	return strings.TrimSpace(localMD5) == strings.TrimSpace(string(remoteMD5)), nil
}
<|endoftext|>


<commit_before>package gengateway

import (
	"bytes"
	"fmt"
	"strings"
	"text/template"

	"github.com/golang/glog"
	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
	"github.com/grpc-ecosystem/grpc-gateway/utilities"
)

type param struct {
	*descriptor.File
	Imports []descriptor.GoPackage
}

type binding struct {
	*descriptor.Binding
}

// HasQueryParam determines if the binding needs parameters in query string.
//
// It sometimes returns true even though actually the binding does not need.
// But it is not serious because it just results in a small amount of extra codes generated.
func (b binding) HasQueryParam() bool {
	if b.Body != nil && len(b.Body.FieldPath) == 0 {
		return false
	}
	fields := make(map[string]bool)
	for _, f := range b.Method.RequestType.Fields {
		fields[f.GetName()] = true
	}
	if b.Body != nil {
		delete(fields, b.Body.FieldPath.String())
	}
	for _, p := range b.PathParams {
		delete(fields, p.FieldPath.String())
	}
	return len(fields) > 0
}

func (b binding) QueryParamFilter() queryParamFilter {
	var seqs [][]string
	if b.Body != nil {
		seqs = append(seqs, strings.Split(b.Body.FieldPath.String(), "."))
	}
	for _, p := range b.PathParams {
		seqs = append(seqs, strings.Split(p.FieldPath.String(), "."))
	}
	return queryParamFilter{utilities.NewDoubleArray(seqs)}
}

// queryParamFilter is a wrapper of utilities.DoubleArray which provides String() to output DoubleArray.Encoding in a stable and predictable format.
type queryParamFilter struct {
	*utilities.DoubleArray
}

func (f queryParamFilter) String() string {
	encodings := make([]string, len(f.Encoding))
	for str, enc := range f.Encoding {
		encodings[enc] = fmt.Sprintf("%q: %d", str, enc)
	}
	e := strings.Join(encodings, ", ")
	return fmt.Sprintf("&utilities.DoubleArray{Encoding: map[string]int{%s}, Base: %#v, Check: %#v}", e, f.Base, f.Check)
}

func applyTemplate(p param) (string, error) {
	w := bytes.NewBuffer(nil)
	if err := headerTemplate.Execute(w, p); err != nil {
		return "", err
	}
	var targetServices []*descriptor.Service
	for _, svc := range p.Services {
		var methodWithBindingsSeen bool
		for _, meth := range svc.Methods {
			glog.V(2).Infof("Processing %s.%s", svc.GetName(), meth.GetName())
			for _, b := range meth.Bindings {
				methodWithBindingsSeen = true
				if err := handlerTemplate.Execute(w, binding{Binding: b}); err != nil {
					return "", err
				}
			}
		}
		if methodWithBindingsSeen {
			targetServices = append(targetServices, svc)
		}
	}
	if len(targetServices) == 0 {
		return "", errNoTargetService
	}
	if err := trailerTemplate.Execute(w, targetServices); err != nil {
		return "", err
	}
	return w.String(), nil
}

var (
	headerTemplate = template.Must(template.New("header").Parse(`
// Code generated by protoc-gen-grpc-gateway
// source: {{.GetName}}
// DO NOT EDIT!

/*
Package {{.GoPkg.Name}} is a reverse proxy.

It translates gRPC into RESTful JSON APIs.
*/
package {{.GoPkg.Name}}
import (
	{{range $i := .Imports}}{{if $i.Standard}}{{$i | printf "%s\n"}}{{end}}{{end}}

	{{range $i := .Imports}}{{if not $i.Standard}}{{$i | printf "%s\n"}}{{end}}{{end}}
)

var _ codes.Code
var _ io.Reader
var _ = runtime.String
var _ = utilities.NewDoubleArray
`))

	handlerTemplate = template.Must(template.New("handler").Parse(`
{{if and .Method.GetClientStreaming .Method.GetServerStreaming}}
{{template "bidi-streaming-request-func" .}}
{{else if .Method.GetClientStreaming}}
{{template "client-streaming-request-func" .}}
{{else}}
{{template "client-rpc-request-func" .}}
{{end}}
`))

	_ = template.Must(handlerTemplate.New("request-func-signature").Parse(strings.Replace(`
{{if .Method.GetServerStreaming}}
func request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ctx context.Context, marshaler runtime.Marshaler, client {{.Method.Service.GetName}}Client, req *http.Request, pathParams map[string]string) ({{.Method.Service.GetName}}_{{.Method.GetName}}Client, runtime.ServerMetadata, error)
{{else}}
func request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ctx context.Context, marshaler runtime.Marshaler, client {{.Method.Service.GetName}}Client, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error)
{{end}}`, "\n", "", -1)))

	_ = template.Must(handlerTemplate.New("client-streaming-request-func").Parse(`
{{template "request-func-signature" .}} {
	var metadata runtime.ServerMetadata
	stream, err := client.{{.Method.GetName}}(ctx)
	if err != nil {
		grpclog.Printf("Failed to start streaming: %v", err)
		return nil, metadata, err
	}
	dec := marshaler.NewDecoder(req.Body)
	for {
		var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
		err = dec.Decode(&protoReq)
		if err == io.EOF {
			break
		}
		if err != nil {
			grpclog.Printf("Failed to decode request: %v", err)
			return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
		}
		if err = stream.Send(&protoReq); err != nil {
			grpclog.Printf("Failed to send request: %v", err)
			return nil, metadata, err
		}
	}

	if err := stream.CloseSend(); err != nil {
		grpclog.Printf("Failed to terminate client stream: %v", err)
		return nil, metadata, err
	}
	header, err := stream.Header()
	if err != nil {
		grpclog.Printf("Failed to get header from client: %v", err)
		return nil, metadata, err
	}
	metadata.HeaderMD = header
{{if .Method.GetServerStreaming}}
	return stream, metadata, nil
{{else}}
	msg, err := stream.CloseAndRecv()
	metadata.TrailerMD = stream.Trailer()
	return msg, metadata, err
{{end}}
}
`))

	_ = template.Must(handlerTemplate.New("client-rpc-request-func").Parse(`
{{if .HasQueryParam}}
var (
	filter_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}} = {{.QueryParamFilter}}
)
{{end}}
{{template "request-func-signature" .}} {
	var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
	var metadata runtime.ServerMetadata
{{if .Body}}
	if err := marshaler.NewDecoder(req.Body).Decode(&{{.Body.RHS "protoReq"}}); err != nil {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
	}
{{end}}
{{if .PathParams}}
	var (
		val string
		ok bool
		err error
		_ = err
	)
	{{range $param := .PathParams}}
	val, ok = pathParams[{{$param | printf "%q"}}]
	if !ok {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", {{$param | printf "%q"}})
	}
{{if $param.IsNestedProto3 }}
	err = runtime.PopulateFieldFromPath(&protoReq, {{$param | printf "%q"}}, val)
{{else}}
	{{$param.RHS "protoReq"}}, err = {{$param.ConvertFuncExpr}}(val)
{{end}}
	if err != nil {
		return nil, metadata, err
	}
	{{end}}
{{end}}
{{if .HasQueryParam}}
	if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}); err != nil {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
	}
{{end}}
{{if .Method.GetServerStreaming}}
	stream, err := client.{{.Method.GetName}}(ctx, &protoReq)
	if err != nil {
		return nil, metadata, err
	}
	header, err := stream.Header()
	if err != nil {
		return nil, metadata, err
	}
	metadata.HeaderMD = header
	return stream, metadata, nil
{{else}}
	msg, err := client.{{.Method.GetName}}(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
	return msg, metadata, err
{{end}}
}`))

	_ = template.Must(handlerTemplate.New("bidi-streaming-request-func").Parse(`
{{template "request-func-signature" .}} {
	var metadata runtime.ServerMetadata
	stream, err := client.{{.Method.GetName}}(ctx)
	if err != nil {
		grpclog.Printf("Failed to start streaming: %v", err)
		return nil, metadata, err
	}
	dec := marshaler.NewDecoder(req.Body)
	handleSend := func() error {
		var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
		err = dec.Decode(&protoReq)
		if err == io.EOF {
			return err
		}
		if err != nil {
			grpclog.Printf("Failed to decode request: %v", err)
			return err
		}
		if err = stream.Send(&protoReq); err != nil {
			grpclog.Printf("Failed to send request: %v", err)
			return err
		}
		return nil
	}
	if err := handleSend(); err != nil {
		if cerr := stream.CloseSend(); cerr != nil {
			grpclog.Printf("Failed to terminate client stream: %v", cerr)
		}
		if err == io.EOF {
			return stream, metadata, nil
		}
		return nil, metadata, err
	}
	go func() {
		for {
			if err := handleSend(); err != nil {
				break
			}
		}
		if err := stream.CloseSend(); err != nil {
			grpclog.Printf("Failed to terminate client stream: %v", err)
		}
	}()
	header, err := stream.Header()
	if err != nil {
		grpclog.Printf("Failed to get header from client: %v", err)
		return nil, metadata, err
	}
	metadata.HeaderMD = header
	return stream, metadata, nil
}
`))

	trailerTemplate = template.Must(template.New("trailer").Parse(`
{{range $svc := .}}
// Register{{$svc.GetName}}HandlerFromEndpoint is same as Register{{$svc.GetName}}Handler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func Register{{$svc.GetName}}HandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
	conn, err := grpc.Dial(endpoint, opts...)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			if cerr := conn.Close(); cerr != nil {
				grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
			}
			return
		}
		go func() {
			<-ctx.Done()
			if cerr := conn.Close(); cerr != nil {
				grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
			}
		}()
	}()

	return Register{{$svc.GetName}}Handler(ctx, mux, conn)
}

// Register{{$svc.GetName}}Handler registers the http handlers for service {{$svc.GetName}} to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func Register{{$svc.GetName}}Handler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
	client := New{{$svc.GetName}}Client(conn)
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	mux.Handle({{$b.HTTPMethod | printf "%q"}}, pattern_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
		ctx, cancel := context.WithCancel(ctx)
		defer cancel()
		if cn, ok := w.(http.CloseNotifier); ok {
			go func(done <-chan struct{}, closed <-chan bool) {
				select {
				case <-done:
				case <-closed:
					cancel()
				}
			}(ctx.Done(), cn.CloseNotify())
		}
		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
		rctx, err := runtime.AnnotateContext(ctx, req)
		if err != nil {
			runtime.HTTPError(ctx, outboundMarshaler, w, req, err)
		}
		resp, md, err := request_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(rctx, inboundMarshaler, client, req, pathParams)
		ctx = runtime.NewServerMetadataContext(ctx, md)
		if err != nil {
			runtime.HTTPError(ctx, outboundMarshaler, w, req, err)
			return
		}
		{{if $m.GetServerStreaming}}
		forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
		{{else}}
		forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
		{{end}}
	})
	{{end}}
	{{end}}
	return nil
}

var (
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	pattern_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}} = runtime.MustPattern(runtime.NewPattern({{$b.PathTmpl.Version}}, {{$b.PathTmpl.OpCodes | printf "%#v"}}, {{$b.PathTmpl.Pool | printf "%#v"}}, {{$b.PathTmpl.Verb | printf "%q"}}))
	{{end}}
	{{end}}
)

var (
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}} = {{if $m.GetServerStreaming}}runtime.ForwardResponseStream{{else}}runtime.ForwardResponseMessage{{end}}
	{{end}}
	{{end}}
)
{{end}}`))
)
<commit_msg>Convert the first letter of method name to upper<commit_after>package gengateway

import (
	"bytes"
	"fmt"
	"strings"
	"text/template"

	"github.com/golang/glog"
	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
	"github.com/grpc-ecosystem/grpc-gateway/utilities"
)

type param struct {
	*descriptor.File
	Imports []descriptor.GoPackage
}

type binding struct {
	*descriptor.Binding
}

// HasQueryParam determines if the binding needs parameters in query string.
//
// It sometimes returns true even though actually the binding does not need.
// But it is not serious because it just results in a small amount of extra codes generated.
func (b binding) HasQueryParam() bool {
	if b.Body != nil && len(b.Body.FieldPath) == 0 {
		return false
	}
	fields := make(map[string]bool)
	for _, f := range b.Method.RequestType.Fields {
		fields[f.GetName()] = true
	}
	if b.Body != nil {
		delete(fields, b.Body.FieldPath.String())
	}
	for _, p := range b.PathParams {
		delete(fields, p.FieldPath.String())
	}
	return len(fields) > 0
}

func (b binding) QueryParamFilter() queryParamFilter {
	var seqs [][]string
	if b.Body != nil {
		seqs = append(seqs, strings.Split(b.Body.FieldPath.String(), "."))
	}
	for _, p := range b.PathParams {
		seqs = append(seqs, strings.Split(p.FieldPath.String(), "."))
	}
	return queryParamFilter{utilities.NewDoubleArray(seqs)}
}

// queryParamFilter is a wrapper of utilities.DoubleArray which provides String() to output DoubleArray.Encoding in a stable and predictable format.
type queryParamFilter struct {
	*utilities.DoubleArray
}

func (f queryParamFilter) String() string {
	encodings := make([]string, len(f.Encoding))
	for str, enc := range f.Encoding {
		encodings[enc] = fmt.Sprintf("%q: %d", str, enc)
	}
	e := strings.Join(encodings, ", ")
	return fmt.Sprintf("&utilities.DoubleArray{Encoding: map[string]int{%s}, Base: %#v, Check: %#v}", e, f.Base, f.Check)
}

func applyTemplate(p param) (string, error) {
	w := bytes.NewBuffer(nil)
	if err := headerTemplate.Execute(w, p); err != nil {
		return "", err
	}
	var targetServices []*descriptor.Service
	for _, svc := range p.Services {
		var methodWithBindingsSeen bool
		for _, meth := range svc.Methods {
			glog.V(2).Infof("Processing %s.%s", svc.GetName(), meth.GetName())
			methName := strings.Title(*meth.Name)
			meth.Name = &methName
			for _, b := range meth.Bindings {
				methodWithBindingsSeen = true
				if err := handlerTemplate.Execute(w, binding{Binding: b}); err != nil {
					return "", err
				}
			}
		}
		if methodWithBindingsSeen {
			targetServices = append(targetServices, svc)
		}
	}
	if len(targetServices) == 0 {
		return "", errNoTargetService
	}
	if err := trailerTemplate.Execute(w, targetServices); err != nil {
		return "", err
	}
	return w.String(), nil
}

var (
	headerTemplate = template.Must(template.New("header").Parse(`
// Code generated by protoc-gen-grpc-gateway
// source: {{.GetName}}
// DO NOT EDIT!

/*
Package {{.GoPkg.Name}} is a reverse proxy.

It translates gRPC into RESTful JSON APIs.
*/
package {{.GoPkg.Name}}
import (
	{{range $i := .Imports}}{{if $i.Standard}}{{$i | printf "%s\n"}}{{end}}{{end}}

	{{range $i := .Imports}}{{if not $i.Standard}}{{$i | printf "%s\n"}}{{end}}{{end}}
)

var _ codes.Code
var _ io.Reader
var _ = runtime.String
var _ = utilities.NewDoubleArray
`))

	handlerTemplate = template.Must(template.New("handler").Parse(`
{{if and .Method.GetClientStreaming .Method.GetServerStreaming}}
{{template "bidi-streaming-request-func" .}}
{{else if .Method.GetClientStreaming}}
{{template "client-streaming-request-func" .}}
{{else}}
{{template "client-rpc-request-func" .}}
{{end}}
`))

	_ = template.Must(handlerTemplate.New("request-func-signature").Parse(strings.Replace(`
{{if .Method.GetServerStreaming}}
func request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ctx context.Context, marshaler runtime.Marshaler, client {{.Method.Service.GetName}}Client, req *http.Request, pathParams map[string]string) ({{.Method.Service.GetName}}_{{.Method.GetName}}Client, runtime.ServerMetadata, error)
{{else}}
func request_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}(ctx context.Context, marshaler runtime.Marshaler, client {{.Method.Service.GetName}}Client, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error)
{{end}}`, "\n", "", -1)))

	_ = template.Must(handlerTemplate.New("client-streaming-request-func").Parse(`
{{template "request-func-signature" .}} {
	var metadata runtime.ServerMetadata
	stream, err := client.{{.Method.GetName}}(ctx)
	if err != nil {
		grpclog.Printf("Failed to start streaming: %v", err)
		return nil, metadata, err
	}
	dec := marshaler.NewDecoder(req.Body)
	for {
		var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
		err = dec.Decode(&protoReq)
		if err == io.EOF {
			break
		}
		if err != nil {
			grpclog.Printf("Failed to decode request: %v", err)
			return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
		}
		if err = stream.Send(&protoReq); err != nil {
			grpclog.Printf("Failed to send request: %v", err)
			return nil, metadata, err
		}
	}

	if err := stream.CloseSend(); err != nil {
		grpclog.Printf("Failed to terminate client stream: %v", err)
		return nil, metadata, err
	}
	header, err := stream.Header()
	if err != nil {
		grpclog.Printf("Failed to get header from client: %v", err)
		return nil, metadata, err
	}
	metadata.HeaderMD = header
{{if .Method.GetServerStreaming}}
	return stream, metadata, nil
{{else}}
	msg, err := stream.CloseAndRecv()
	metadata.TrailerMD = stream.Trailer()
	return msg, metadata, err
{{end}}
}
`))

	_ = template.Must(handlerTemplate.New("client-rpc-request-func").Parse(`
{{if .HasQueryParam}}
var (
	filter_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}} = {{.QueryParamFilter}}
)
{{end}}
{{template "request-func-signature" .}} {
	var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
	var metadata runtime.ServerMetadata
{{if .Body}}
	if err := marshaler.NewDecoder(req.Body).Decode(&{{.Body.RHS "protoReq"}}); err != nil {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
	}
{{end}}
{{if .PathParams}}
	var (
		val string
		ok bool
		err error
		_ = err
	)
	{{range $param := .PathParams}}
	val, ok = pathParams[{{$param | printf "%q"}}]
	if !ok {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", {{$param | printf "%q"}})
	}
{{if $param.IsNestedProto3 }}
	err = runtime.PopulateFieldFromPath(&protoReq, {{$param | printf "%q"}}, val)
{{else}}
	{{$param.RHS "protoReq"}}, err = {{$param.ConvertFuncExpr}}(val)
{{end}}
	if err != nil {
		return nil, metadata, err
	}
	{{end}}
{{end}}
{{if .HasQueryParam}}
	if err := runtime.PopulateQueryParameters(&protoReq, req.URL.Query(), filter_{{.Method.Service.GetName}}_{{.Method.GetName}}_{{.Index}}); err != nil {
		return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err)
	}
{{end}}
{{if .Method.GetServerStreaming}}
	stream, err := client.{{.Method.GetName}}(ctx, &protoReq)
	if err != nil {
		return nil, metadata, err
	}
	header, err := stream.Header()
	if err != nil {
		return nil, metadata, err
	}
	metadata.HeaderMD = header
	return stream, metadata, nil
{{else}}
	msg, err := client.{{.Method.GetName}}(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
	return msg, metadata, err
{{end}}
}`))

	_ = template.Must(handlerTemplate.New("bidi-streaming-request-func").Parse(`
{{template "request-func-signature" .}} {
	var metadata runtime.ServerMetadata
	stream, err := client.{{.Method.GetName}}(ctx)
	if err != nil {
		grpclog.Printf("Failed to start streaming: %v", err)
		return nil, metadata, err
	}
	dec := marshaler.NewDecoder(req.Body)
	handleSend := func() error {
		var protoReq {{.Method.RequestType.GoType .Method.Service.File.GoPkg.Path}}
		err = dec.Decode(&protoReq)
		if err == io.EOF {
			return err
		}
		if err != nil {
			grpclog.Printf("Failed to decode request: %v", err)
			return err
		}
		if err = stream.Send(&protoReq); err != nil {
			grpclog.Printf("Failed to send request: %v", err)
			return err
		}
		return nil
	}
	if err := handleSend(); err != nil {
		if cerr := stream.CloseSend(); cerr != nil {
			grpclog.Printf("Failed to terminate client stream: %v", cerr)
		}
		if err == io.EOF {
			return stream, metadata, nil
		}
		return nil, metadata, err
	}
	go func() {
		for {
			if err := handleSend(); err != nil {
				break
			}
		}
		if err := stream.CloseSend(); err != nil {
			grpclog.Printf("Failed to terminate client stream: %v", err)
		}
	}()
	header, err := stream.Header()
	if err != nil {
		grpclog.Printf("Failed to get header from client: %v", err)
		return nil, metadata, err
	}
	metadata.HeaderMD = header
	return stream, metadata, nil
}
`))

	trailerTemplate = template.Must(template.New("trailer").Parse(`
{{range $svc := .}}
// Register{{$svc.GetName}}HandlerFromEndpoint is same as Register{{$svc.GetName}}Handler but
// automatically dials to "endpoint" and closes the connection when "ctx" gets done.
func Register{{$svc.GetName}}HandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) {
	conn, err := grpc.Dial(endpoint, opts...)
	if err != nil {
		return err
	}
	defer func() {
		if err != nil {
			if cerr := conn.Close(); cerr != nil {
				grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
			}
			return
		}
		go func() {
			<-ctx.Done()
			if cerr := conn.Close(); cerr != nil {
				grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr)
			}
		}()
	}()

	return Register{{$svc.GetName}}Handler(ctx, mux, conn)
}

// Register{{$svc.GetName}}Handler registers the http handlers for service {{$svc.GetName}} to "mux".
// The handlers forward requests to the grpc endpoint over "conn".
func Register{{$svc.GetName}}Handler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error {
	client := New{{$svc.GetName}}Client(conn)
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	mux.Handle({{$b.HTTPMethod | printf "%q"}}, pattern_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
		ctx, cancel := context.WithCancel(ctx)
		defer cancel()
		if cn, ok := w.(http.CloseNotifier); ok {
			go func(done <-chan struct{}, closed <-chan bool) {
				select {
				case <-done:
				case <-closed:
					cancel()
				}
			}(ctx.Done(), cn.CloseNotify())
		}
		inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
		rctx, err := runtime.AnnotateContext(ctx, req)
		if err != nil {
			runtime.HTTPError(ctx, outboundMarshaler, w, req, err)
		}
		resp, md, err := request_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(rctx, inboundMarshaler, client, req, pathParams)
		ctx = runtime.NewServerMetadataContext(ctx, md)
		if err != nil {
			runtime.HTTPError(ctx, outboundMarshaler, w, req, err)
			return
		}
		{{if $m.GetServerStreaming}}
		forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, outboundMarshaler, w, req, func() (proto.Message, error) { return resp.Recv() }, mux.GetForwardResponseOptions()...)
		{{else}}
		forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}}(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
		{{end}}
	})
	{{end}}
	{{end}}
	return nil
}

var (
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	pattern_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}} = runtime.MustPattern(runtime.NewPattern({{$b.PathTmpl.Version}}, {{$b.PathTmpl.OpCodes | printf "%#v"}}, {{$b.PathTmpl.Pool | printf "%#v"}}, {{$b.PathTmpl.Verb | printf "%q"}}))
	{{end}}
	{{end}}
)

var (
	{{range $m := $svc.Methods}}
	{{range $b := $m.Bindings}}
	forward_{{$svc.GetName}}_{{$m.GetName}}_{{$b.Index}} = {{if $m.GetServerStreaming}}runtime.ForwardResponseStream{{else}}runtime.ForwardResponseMessage{{end}}
	{{end}}
	{{end}}
)
{{end}}`))
)
<|endoftext|>


<commit_before>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package upload

import (
	"os"
	"testing"

	"github.com/issue9/assert"
)

func TestNew(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)
	// 自动转换成小写，且加上最前面的.符号
	a.Equal(u.exts, []string{".gif", ".png", ".gif"})
	a.Equal(u.dir, "./testdir"+string(os.PathSeparator))

	// dir为一个文件
	u, err = New("./testdir/file", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.Error(err).Nil(u)
}

func TestUpload_isAllowExt(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)
	a.True(u.isAllowExt(".gif"))
	a.True(u.isAllowExt(".png"))

	a.False(u.isAllowExt(".TXT"))
	a.False(u.isAllowExt(""))
	a.False(u.isAllowExt(".png"))
	a.False(u.isAllowExt(".exe"))
}

func TestUpload_getDestPath(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)

	t.Log(u.getDestPath(".png"))
	t.Log(u.getDestPath(".jpeg"))
}
<commit_msg>修正测试错误<commit_after>// Copyright 2015 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package upload

import (
	"os"
	"testing"

	"github.com/issue9/assert"
)

func TestNew(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)
	// 自动转换成小写，且加上最前面的.符号
	a.Equal(u.exts, []string{".gif", ".png", ".gif"})
	a.Equal(u.dir, "./testdir"+string(os.PathSeparator))

	// dir为一个文件
	u, err = New("./testdir/file", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.Error(err).Nil(u)
}

func TestUpload_isAllowExt(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)
	a.True(u.isAllowExt(".gif"))
	a.True(u.isAllowExt(".png"))

	a.False(u.isAllowExt(".TXT"))
	a.False(u.isAllowExt(""))
	a.False(u.isAllowExt("png"))
	a.False(u.isAllowExt(".exe"))
}

func TestUpload_getDestPath(t *testing.T) {
	a := assert.New(t)

	u, err := New("./testdir", "2006/01/02/", 10*1024, "gif", ".png", ".GIF")
	a.NotError(err).NotNil(u)

	t.Log(u.getDestPath(".png"))
	t.Log(u.getDestPath(".jpeg"))
}
<|endoftext|>


<commit_before>/*
Copyright 2021 The Vitess 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 vstreamer

import (
	"math"
	"math/rand"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

type polynomial []float64

func (p polynomial) fit(x float64) float64 {
	var y float64
	for i, exp := range p {
		y += exp * math.Pow(x, float64(i))
	}
	return y
}

func simulate(t *testing.T, ps PacketSizer, base, mustSend int, interpolate func(float64) float64) (time.Duration, int) {
	t.Helper()

	var elapsed time.Duration
	var sent int
	var sentPkt int
	packetRange := float64(base) * 10.0

	packetSize := 0
	for sent < mustSend {
		packetSize += rand.Intn(base / 100)

		if ps.ShouldSend(packetSize) {
			x := float64(packetSize) / packetRange
			y := interpolate(x)
			d := time.Duration(float64(time.Microsecond) * y * float64(packetSize))
			ps.Record(packetSize, d)

			sent += packetSize
			elapsed += d
			sentPkt++

			packetSize = 0
		}
	}
	return elapsed, sentPkt
}

func TestPacketSizeSimulation(t *testing.T) {
	cases := []struct {
		name     string
		baseSize int
		p        polynomial
	}{
		{
			name:     "growth with tapper",
			baseSize: 25000,
			p:        polynomial{0.767, 1.278, -12.048, 25.262, -21.270, 6.410},
		},
		{
			name:     "growth without tapper",
			baseSize: 25000,
			p:        polynomial{0.473, 5.333, -38.663, 90.731, -87.005, 30.128},
		},
		{
			name:     "regression",
			baseSize: 25000,
			p:        polynomial{0.247, -0.726, 2.864, -3.022, 2.273, -0.641},
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			// Simulate a replication using the given polynomial and the dynamic packet sizer
			ps1 := newDynamicPacketSizer(tc.baseSize)
			elapsed1, sent1 := simulate(t, ps1, tc.baseSize, tc.baseSize*1000, tc.p.fit)

			// Simulate the same polynomial using a fixed packet size
			ps2 := newFixedPacketSize(tc.baseSize)
			elapsed2, sent2 := simulate(t, ps2, tc.baseSize, tc.baseSize*1000, tc.p.fit)

			// the simulation for dynamic packet sizing should always be faster then the fixed packet,
			// and should also send fewer packets in total
			require.True(t, elapsed1 < elapsed2)
			require.True(t, sent1 < sent2)
			// t.Logf("dynamic = (%v, %d), fixed = (%v, %d)", elapsed1, sent1, elapsed2, sent2)
		})
	}
}
<commit_msg>vstream: add error margins to tests<commit_after>/*
Copyright 2021 The Vitess 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 vstreamer

import (
	"math"
	"math/rand"
	"testing"
	"time"
)

type polynomial []float64

func (p polynomial) fit(x float64) float64 {
	var y float64
	for i, exp := range p {
		y += exp * math.Pow(x, float64(i))
	}
	return y
}

func simulate(t *testing.T, ps PacketSizer, base, mustSend int, interpolate func(float64) float64) (time.Duration, int) {
	t.Helper()

	var elapsed time.Duration
	var sent int
	var sentPkt int
	packetRange := float64(base) * 10.0

	packetSize := 0
	for sent < mustSend {
		packetSize += rand.Intn(base / 100)

		if ps.ShouldSend(packetSize) {
			x := float64(packetSize) / packetRange
			y := interpolate(x)
			d := time.Duration(float64(time.Microsecond) * y * float64(packetSize))
			ps.Record(packetSize, d)

			sent += packetSize
			elapsed += d
			sentPkt++

			packetSize = 0
		}
	}
	return elapsed, sentPkt
}

func TestPacketSizeSimulation(t *testing.T) {
	cases := []struct {
		name     string
		baseSize int
		p        polynomial
		error    time.Duration
	}{
		{
			name:     "growth with tapper",
			baseSize: 25000,
			p:        polynomial{0.767, 1.278, -12.048, 25.262, -21.270, 6.410},
		},
		{
			name:     "growth without tapper",
			baseSize: 25000,
			p:        polynomial{0.473, 5.333, -38.663, 90.731, -87.005, 30.128},
			error:    5 * time.Millisecond,
		},
		{
			name:     "regression",
			baseSize: 25000,
			p:        polynomial{0.247, -0.726, 2.864, -3.022, 2.273, -0.641},
			error:    1 * time.Second,
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			seed := time.Now().UnixNano()
			rand.Seed(seed)

			// Simulate a replication using the given polynomial and the dynamic packet sizer
			ps1 := newDynamicPacketSizer(tc.baseSize)
			elapsed1, sent1 := simulate(t, ps1, tc.baseSize, tc.baseSize*1000, tc.p.fit)

			// Simulate the same polynomial using a fixed packet size
			ps2 := newFixedPacketSize(tc.baseSize)
			elapsed2, sent2 := simulate(t, ps2, tc.baseSize, tc.baseSize*1000, tc.p.fit)

			// the simulation for dynamic packet sizing should always be faster then the fixed packet,
			// and should also send fewer packets in total
			delta := elapsed1 - elapsed2
			if delta > tc.error {
				t.Errorf("packet-adjusted simulation is %v slower than fixed approach, seed %d", delta, seed)
			}
			if sent1 > sent2 {
				t.Errorf("packet-adjusted simulation sent more packets (%d) than fixed approach (%d), seed %d", sent1, sent2, seed)
			}
			// t.Logf("dynamic = (%v, %d), fixed = (%v, %d)", elapsed1, sent1, elapsed2, sent2)
		})
	}
}
<|endoftext|>