<commit_before>package start

import (
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/laurent22/toml-go"
	. "github.com/smartystreets/goconvey/convey"
)

func TestReadTomlFile(t *testing.T) {
	Convey("Given a file \"test.toml\" in test/", t, func() {
		var tomlDoc toml.Document
		var err error
		cfg := new(ConfigFile)

		Convey("then readTomlFile('./test/test.toml') should find the file", func() {
			tomlDoc, err = cfg.readTomlFile("./test/test.toml")
			So(err, ShouldBeNil)

			Convey("and it should read all test values", func() {
				So(tomlDoc.GetString("astring"), ShouldEqual, "Hello")
				So(tomlDoc.GetBool("abool"), ShouldEqual, true)
				So(tomlDoc.GetInt("anint"), ShouldEqual, 42)
				So(tomlDoc.GetDate("adate").Equal(time.Date(2014, time.August, 17, 9, 25, 0, 0, time.UTC)), ShouldBeTrue)
			})
		})
	})
}

func TestConfigFile(t *testing.T) {
	Convey("When passing an absolute path to an existing TOML file to NewConfigFile", t, func() {
		tomlfile, err := filepath.Abs("test/test.toml")
		So(err, ShouldBeNil)

		Convey("then NewConfigFile loads "+tomlfile+" and returns a new ConfigFile", func() {
			cfg := NewConfigFile(tomlfile)
			So(cfg, ShouldNotBeNil)
		})

	})

	Convey("When passing an absolute directory to NewConfigFile", t, func() {
		tomlfile, err := filepath.Abs("test")
		So(err, ShouldBeNil)

		Convey("then NewConfigFile loads "+appName()+".toml from that directory and returns a new ConfigFile", func() {
			cfg := NewConfigFile(tomlfile)
			So(cfg, ShouldNotBeNil)
		})

	})

	Convey("When passing just a file name to NewConfigFile", t, func() {
		tomlname := "start_test.toml"
		var tomlpath string

		Convey("and the file exists in the home directory", func() {
			home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
			if home == "" {
				home = os.Getenv("USERPROFILE")
			}
			if home == "" {
				home = os.Getenv("HOME")
			}
			tomlpath = filepath.Join(home, tomlname)
			_, err := os.Create(tomlpath)
			if err != nil {
				panic(err)
			}

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile(tomlname)
				So(cfg, ShouldNotBeNil)
			})
		})

		Convey("and the file is specified by the env var "+strings.ToUpper(appName())+"_CFGPATH", func() {
			os.Setenv(strings.ToUpper(appName())+"_CFGPATH", "test/test.toml")

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile("")
				So(cfg, ShouldNotBeNil)
			})

		})

		Convey("and the file is in the working directory", func() {
			pwd, _ := os.Getwd()
			tomlpath = filepath.Join(pwd, tomlname)
			os.Create(tomlpath)

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile(tomlname)
				So(cfg, ShouldNotBeNil)
			})
		})

		Reset(func() {
			os.Remove(tomlpath)
			os.Setenv(strings.ToUpper(os.Args[0])+"_CFGPATH", "")
		})
	})
}
<commit_msg>Some Cosmetic changes. Hardcoded the app name as "start", as go test always generates the name "start.test".<commit_after>package start

import (
	"os"
	"path/filepath"
	"testing"
	"time"

	"github.com/laurent22/toml-go"
	. "github.com/smartystreets/goconvey/convey"
)

func TestReadTomlFile(t *testing.T) {
	Convey("Given a file \"test.toml\" in test/", t, func() {
		var tomlDoc toml.Document
		var err error
		cfg := new(ConfigFile)

		Convey("then readTomlFile('./test/test.toml') should find the file", func() {
			tomlDoc, err = cfg.readTomlFile("./test/test.toml")
			So(err, ShouldBeNil)

			Convey("and it should read all test values", func() {
				So(tomlDoc.GetString("astring"), ShouldEqual, "From Config File")
				So(tomlDoc.GetBool("abool"), ShouldEqual, true)
				So(tomlDoc.GetInt("anint"), ShouldEqual, 42)
				So(tomlDoc.GetDate("adate").Equal(time.Date(2014, time.August, 17, 9, 25, 0, 0, time.UTC)), ShouldBeTrue)
			})
		})
	})
}

func TestConfigFile(t *testing.T) {
	Convey("When passing an absolute path to an existing TOML file to NewConfigFile", t, func() {
		tomlfile, err := filepath.Abs("test/test.toml")
		So(err, ShouldBeNil)

		Convey("then NewConfigFile loads "+tomlfile+" and returns a new ConfigFile", func() {
			cfg := NewConfigFile(tomlfile)
			So(cfg, ShouldNotBeNil)
		})

	})

	Convey("When passing an absolute directory to NewConfigFile", t, func() {
		tomlfile, err := filepath.Abs("test")
		So(err, ShouldBeNil)

		Convey("then NewConfigFile loads start.toml from that directory and returns a new ConfigFile", func() {
			cfg := NewConfigFile(tomlfile)
			So(cfg, ShouldNotBeNil)
			Convey("and AppName() should return start", func() {
				So(AppName(), ShouldEqual, "start")
			})
		})
	})

	Convey("When passing just a file name to NewConfigFile", t, func() {
		tomlname := "custom.toml"
		var tomlpath string

		Convey("and the file exists in the home directory", func() {
			home := os.Getenv("HOMEDRIVE") + os.Getenv("HOMEPATH")
			if home == "" {
				home = os.Getenv("USERPROFILE")
			}
			if home == "" {
				home = os.Getenv("HOME")
			}
			tomlpath = filepath.Join(home, tomlname)
			_, err := os.Create(tomlpath)
			if err != nil {
				panic(err)
			}

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile(tomlname)
				So(cfg, ShouldNotBeNil)
			})
		})

		Convey("and the file is specified by the env var START_CFGPATH", func() {
			os.Setenv("START_CFGPATH", "test/test.toml")

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile("")
				So(cfg, ShouldNotBeNil)
			})

		})

		Convey("and the file is in the working directory", func() {
			pwd, _ := os.Getwd()
			tomlpath = filepath.Join(pwd, tomlname)
			os.Create(tomlpath)

			Convey("then NewConfigFile should find the file", func() {
				cfg := NewConfigFile(tomlname)
				So(cfg, ShouldNotBeNil)
			})
		})

		Reset(func() {
			os.Remove(tomlpath)
			os.Setenv("START_CFGPATH", "")
		})
	})
}
<|endoftext|>


<commit_before>package chef

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"reflect"
	"strings"
	"testing"
)

var testRequiredHeaders []string

var ConfigFilePath = "test/support/TEST_CONFIG.json"

func init() {
	testRequiredHeaders = []string{
		"Accept",
		"X-Ops-Timestamp",
		"X-Ops-Userid",
		"X-Ops-Sign",
		"X-Ops-Content-Hash",
		"X-Ops-Authorization-1",
	}
}

func testConnectionWrapper(t *testing.T) *Chef {
	chef, err := Connect()
	if err != nil {
		t.Fatal(err)
	}
	chef.SSLNoVerify = true
	chef.Version = "11.6.0"

	return chef
}

type testConfigFile struct {
	RequiredCookbook struct {
		Name    string `json:"name"`
		Version string `json:"version"`
	} `json:"required_cookbook"`
	RequiredNode struct {
		Name string `json:"name"`
	} `json:"required_node"`
	RequiredRecipe struct {
		Name string `json:"name"`
	} `json:"required_recipe"`
	RequiredRole struct {
		Name string `json:"name"`
	} `json:"required_role"`
	RequiredClient struct {
		Name string `json:"name"`
	} `json:"required_client"`
	RequiredEnvironment struct {
		Name string `json:"name"`
	} `json:"required_environment"`
	RequiredUser struct {
		Name string `json:"name"`
	} `json:"required_user"`
	RequiredData struct {
		Name string `json:"name"`
	} `json:"required_data"`
	SearchData struct {
		Index string `json:"index"`
		Query string `json:"query"`
	} `json:"search_data"`
	TestCredentials struct {
		Host    string `json:"host"`
		Port    string `json:"port"`
		Version string `json:"version"`
		UserId  string `json:"user_name"`
		Key     string `json:"key"`
	} `json:"test_credentials"`
	RequiredPrincipal struct {
		Name string `json:"name"`
	} `json:"required_principal"`
	KeyPath   string `json:"key_path"`
	KeyString string `json:"key_string"`
}

func testConfig() *testConfigFile {
	file, err := ioutil.ReadFile(ConfigFilePath)
	t := new(testing.T)
	if err != nil {
		t.Error(err)
	}

	var config *testConfigFile
	json.Unmarshal(file, &config)
	if config == nil {
		t.Error("Config is nil")
	}
	return config
}

func TestReadConfig(t *testing.T) {
	_ = testConfig()
}

func TestHashStr(t *testing.T) {
	if len(hashStr("hash_this")) != 28 {
		t.Error("Wrong length for hashAndBase64")
	}
}

func TestResponseBody(t *testing.T) {
	etsy, err := http.Get("https://www.etsy.com/")
	if err != nil {
		t.Error(err)
	}

	bytes, err := responseBody(etsy)
	if err != nil {
		t.Error(err)
	}

	etsyString := "Is code your craft? http://www.etsy.com/careers"
	if !strings.Contains(string(bytes), etsyString) {
		t.Error("Response body didn't return valid string")
	}
}

func TestConnectCredentials(t *testing.T) {
	config := testConfig()
	host := config.TestCredentials.Host
	port := config.TestCredentials.Port
	version := config.TestCredentials.Version
	userid := config.TestCredentials.UserId
	key := config.TestCredentials.Key
	_, err := ConnectCredentials(host, port, version, userid, key)
	if err != nil {
		t.Error(err)
	}
}

func TestConnectUrl(t *testing.T) {
	config := testConfig()

	var url string
	switch config.TestCredentials.Port {
	case "443":
		url = fmt.Sprintf("https://%s", config.TestCredentials.Host)
	case "80":
		url = fmt.Sprintf("http://%s", config.TestCredentials.Host)
	default:
		url = fmt.Sprintf("%s:%s", config.TestCredentials.Host, config.TestCredentials.Port)
	}

	c, err := ConnectUrl(url, "0.0.1", config.TestCredentials.UserId, config.TestCredentials.Key)
	if err != nil {
		t.Fatal(err)
	}

	if c.UserId != config.TestCredentials.UserId {
		t.Fatal("credentials don't match")
	}

}

func TestGet(t *testing.T) {
	c := testConnectionWrapper(t)
	resp, err := c.Get("/cookbooks")
	if err != nil {
		t.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	cookbooks := map[string]interface{}{}
	json.Unmarshal(body, &cookbooks)
	found := false
	config := testConfig()
	cookbook := config.RequiredCookbook.Name
	for name := range cookbooks {
		if name == cookbook {
			found = true
			break
		}
	}
	if !found {
		t.Error("Required cookbook not found")
	}
}

func TestGetWithParams(t *testing.T) {
	type searchResults struct {
		Total int           `json:"total"`
		Start int           `json:"start"`
		Rows  []interface{} `json:"rows"`
	}

	c := testConnectionWrapper(t)
	params := make(map[string]string)
	params["q"] = "name:neo4j*"

	resp, err := c.GetWithParams("/search/node", params)
	if err != nil {
		t.Error(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	res := new(searchResults)
	json.Unmarshal(body, &res)

	if res.Total == 0 {
		t.Fatal("query result is empty: ", res)
	}
}

func TestPost(t *testing.T) {
	c := testConnectionWrapper(t)
	config := testConfig()
	cookbook := config.RequiredCookbook.Name
	run_list := strings.NewReader(fmt.Sprintf(`{ "run_list": [ "%s" ] }`, cookbook))
	resp, err := c.Post("/environments/_default/cookbook_versions", nil, run_list)
	if err != nil {
		t.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	// This could or should be better. Be good to have another
	// test for unsolvable run_list
	cookbooks := map[string]interface{}{}
	json.Unmarshal(body, &cookbooks)
	found := false
	for name := range cookbooks {
		if name == cookbook {
			found = true
			break
		}
	}
	if !found {
		t.Error("Cookbook not solved")
	}

	// Test partial search via post. Should be in search probably, but function
	// is in api raw post method.
	partial_body := strings.NewReader(` { 'name' => [ 'name' ] } `)
	params := make(map[string]string)
	params["q"] = "name:neo4j*"

	// For now this isn't supported in goiardi, but we can still submit it.
	resp, err = c.Post("/search/node", params, partial_body)
	if err != nil {
		t.Error(err)
	}
	// TODO: make this work better

}

func TestConnect(t *testing.T) {
	if _, err := Connect(); err != nil {
		t.Error(err)
	}
}

func TestGenerateRequestAuthorization(t *testing.T) {
	chef := testConnectionWrapper(t)
	request, err := http.NewRequest("GET", chef.requestUrl("/cookbooks"), nil)
	auth, err := chef.generateRequestAuthorization(request)
	if err != nil {
		t.Fatal(err)
	}
	if len(auth[0]) != 60 {
		t.Error("Incorrect request authorization string")
	}
}

func TestApiRequestHeaders(t *testing.T) {
	chef := testConnectionWrapper(t)
	request, _ := http.NewRequest("GET", chef.requestUrl("/cookbooks"), nil)
	err := chef.apiRequestHeaders(request)
	if err != nil {
		println("failed to generate RequestHeaders")
		t.Fatal(err)
	}
	count := 0
	for _, requiredHeader := range testRequiredHeaders {
		for header := range request.Header {
			if strings.ToLower(requiredHeader) == strings.ToLower(header) {
				count += 1
				break
			}
		}
	}
	if count != len(testRequiredHeaders) {
		t.Error("apiRequestHeaders didn't return all of testRequiredHeaders")
	}
}

func TestPrivateEncrypt(t *testing.T) {
	chef := testConnectionWrapper(t)
	enc, err := chef.privateEncrypt([]byte("encrypt_this"))
	if err != nil {
		t.Error(err)
	}
	if len(enc) != 256 {
		t.Error("Wrong size of encrypted data")
	}
}

func TestBase64BlockEncode(t *testing.T) {
	toEncode := []byte("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
	results := base64BlockEncode(toEncode)
	expected := []string{"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJz", "dHV2d3h5emFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6"}
	if !reflect.DeepEqual(results, expected) {
		t.Error("Results not matching")
	}
}

func TestKeyFromString(t *testing.T) {
	config := testConfig()
	_, err := keyFromString([]byte(config.KeyString))
	if err != nil {
		t.Error(err)
	}
}

func TestKeyFromFile(t *testing.T) {
	config := testConfig()
	_, err := keyFromFile(config.KeyPath)
	if err != nil {
		t.Error(err)
	}
}

func TestSplitWhitespace(t *testing.T) {
	str := "c   h   e   f"
	if !reflect.DeepEqual(splitWhitespace(str), []string{"c", "h", "e", "f"}) {
		t.Error("splitWhitespace slices not equal")
	}
}

func TestFilterQuotes(t *testing.T) {
	known := map[string]string{
		`'this`: "this",
		`this'`: "this",
		`"this`: "this",
		`this"`: "this",
	}

	for bad, good := range known {
		if filterQuotes(bad) != good {
			t.Error("filterQuotes didn't produce an expected string")
		}
	}
}
<commit_msg>add test for buildQueryString<commit_after>package chef

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"reflect"
	"strings"
	"testing"
)

var testRequiredHeaders []string

var ConfigFilePath = "test/support/TEST_CONFIG.json"

func init() {
	testRequiredHeaders = []string{
		"Accept",
		"X-Ops-Timestamp",
		"X-Ops-Userid",
		"X-Ops-Sign",
		"X-Ops-Content-Hash",
		"X-Ops-Authorization-1",
	}
}

func testConnectionWrapper(t *testing.T) *Chef {
	chef, err := Connect()
	if err != nil {
		t.Fatal(err)
	}
	chef.SSLNoVerify = true
	chef.Version = "11.6.0"

	return chef
}

type testConfigFile struct {
	RequiredCookbook struct {
		Name    string `json:"name"`
		Version string `json:"version"`
	} `json:"required_cookbook"`
	RequiredNode struct {
		Name string `json:"name"`
	} `json:"required_node"`
	RequiredRecipe struct {
		Name string `json:"name"`
	} `json:"required_recipe"`
	RequiredRole struct {
		Name string `json:"name"`
	} `json:"required_role"`
	RequiredClient struct {
		Name string `json:"name"`
	} `json:"required_client"`
	RequiredEnvironment struct {
		Name string `json:"name"`
	} `json:"required_environment"`
	RequiredUser struct {
		Name string `json:"name"`
	} `json:"required_user"`
	RequiredData struct {
		Name string `json:"name"`
	} `json:"required_data"`
	SearchData struct {
		Index string `json:"index"`
		Query string `json:"query"`
	} `json:"search_data"`
	TestCredentials struct {
		Host    string `json:"host"`
		Port    string `json:"port"`
		Version string `json:"version"`
		UserId  string `json:"user_name"`
		Key     string `json:"key"`
	} `json:"test_credentials"`
	RequiredPrincipal struct {
		Name string `json:"name"`
	} `json:"required_principal"`
	KeyPath   string `json:"key_path"`
	KeyString string `json:"key_string"`
}

func testConfig() *testConfigFile {
	file, err := ioutil.ReadFile(ConfigFilePath)
	t := new(testing.T)
	if err != nil {
		t.Error(err)
	}

	var config *testConfigFile
	json.Unmarshal(file, &config)
	if config == nil {
		t.Error("Config is nil")
	}
	return config
}

func TestReadConfig(t *testing.T) {
	_ = testConfig()
}

func TestHashStr(t *testing.T) {
	if len(hashStr("hash_this")) != 28 {
		t.Error("Wrong length for hashAndBase64")
	}
}

func TestResponseBody(t *testing.T) {
	etsy, err := http.Get("https://www.etsy.com/")
	if err != nil {
		t.Error(err)
	}

	bytes, err := responseBody(etsy)
	if err != nil {
		t.Error(err)
	}

	etsyString := "Is code your craft? http://www.etsy.com/careers"
	if !strings.Contains(string(bytes), etsyString) {
		t.Error("Response body didn't return valid string")
	}
}

func TestConnectCredentials(t *testing.T) {
	config := testConfig()
	host := config.TestCredentials.Host
	port := config.TestCredentials.Port
	version := config.TestCredentials.Version
	userid := config.TestCredentials.UserId
	key := config.TestCredentials.Key
	_, err := ConnectCredentials(host, port, version, userid, key)
	if err != nil {
		t.Error(err)
	}
}

func TestConnectUrl(t *testing.T) {
	config := testConfig()

	var url string
	switch config.TestCredentials.Port {
	case "443":
		url = fmt.Sprintf("https://%s", config.TestCredentials.Host)
	case "80":
		url = fmt.Sprintf("http://%s", config.TestCredentials.Host)
	default:
		url = fmt.Sprintf("%s:%s", config.TestCredentials.Host, config.TestCredentials.Port)
	}

	c, err := ConnectUrl(url, "0.0.1", config.TestCredentials.UserId, config.TestCredentials.Key)
	if err != nil {
		t.Fatal(err)
	}

	if c.UserId != config.TestCredentials.UserId {
		t.Fatal("credentials don't match")
	}

}

func TestBuildQueryString(t *testing.T) {
	c := testConnectionWrapper(t)

	params := make(map[string]string)
	params["foo"] = "bar"
	s, err := c.buildQueryString("cookbooks", params)
	if err != nil {
		t.Fatal(err)
	}

	if s != "http://127.0.0.1:8443/cookbooks?foo=bar" {
		t.Fatal("assembled uri doesn't match", s)
	}
}

func TestGet(t *testing.T) {
	c := testConnectionWrapper(t)
	resp, err := c.Get("/cookbooks")
	if err != nil {
		t.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	cookbooks := map[string]interface{}{}
	json.Unmarshal(body, &cookbooks)
	found := false
	config := testConfig()
	cookbook := config.RequiredCookbook.Name
	for name := range cookbooks {
		if name == cookbook {
			found = true
			break
		}
	}
	if !found {
		t.Error("Required cookbook not found")
	}
}

func TestGetWithParams(t *testing.T) {
	type searchResults struct {
		Total int           `json:"total"`
		Start int           `json:"start"`
		Rows  []interface{} `json:"rows"`
	}

	c := testConnectionWrapper(t)
	params := make(map[string]string)
	params["q"] = "name:neo4j*"

	resp, err := c.GetWithParams("/search/node", params)
	if err != nil {
		t.Error(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	res := new(searchResults)
	json.Unmarshal(body, &res)

	if res.Total == 0 {
		t.Fatal("query result is empty: ", res)
	}
}

func TestPost(t *testing.T) {
	c := testConnectionWrapper(t)
	config := testConfig()
	cookbook := config.RequiredCookbook.Name
	run_list := strings.NewReader(fmt.Sprintf(`{ "run_list": [ "%s" ] }`, cookbook))
	resp, err := c.Post("/environments/_default/cookbook_versions", nil, run_list)
	if err != nil {
		t.Fatal(err)
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Error(err)
	}

	// This could or should be better. Be good to have another
	// test for unsolvable run_list
	cookbooks := map[string]interface{}{}
	json.Unmarshal(body, &cookbooks)
	found := false
	for name := range cookbooks {
		if name == cookbook {
			found = true
			break
		}
	}
	if !found {
		t.Error("Cookbook not solved")
	}

	// Test partial search via post. Should be in search probably, but function
	// is in api raw post method.
	partial_body := strings.NewReader(` { 'name' => [ 'name' ] } `)
	params := make(map[string]string)
	params["q"] = "name:neo4j*"

	// For now this isn't supported in goiardi, but we can still submit it.
	resp, err = c.Post("/search/node", params, partial_body)
	if err != nil {
		t.Error(err)
	}
	// TODO: make this work better

}

func TestConnect(t *testing.T) {
	if _, err := Connect(); err != nil {
		t.Error(err)
	}
}

func TestGenerateRequestAuthorization(t *testing.T) {
	chef := testConnectionWrapper(t)
	request, err := http.NewRequest("GET", chef.requestUrl("/cookbooks"), nil)
	auth, err := chef.generateRequestAuthorization(request)
	if err != nil {
		t.Fatal(err)
	}
	if len(auth[0]) != 60 {
		t.Error("Incorrect request authorization string")
	}
}

func TestApiRequestHeaders(t *testing.T) {
	chef := testConnectionWrapper(t)
	request, _ := http.NewRequest("GET", chef.requestUrl("/cookbooks"), nil)
	err := chef.apiRequestHeaders(request)
	if err != nil {
		println("failed to generate RequestHeaders")
		t.Fatal(err)
	}
	count := 0
	for _, requiredHeader := range testRequiredHeaders {
		for header := range request.Header {
			if strings.ToLower(requiredHeader) == strings.ToLower(header) {
				count += 1
				break
			}
		}
	}
	if count != len(testRequiredHeaders) {
		t.Error("apiRequestHeaders didn't return all of testRequiredHeaders")
	}
}

func TestPrivateEncrypt(t *testing.T) {
	chef := testConnectionWrapper(t)
	enc, err := chef.privateEncrypt([]byte("encrypt_this"))
	if err != nil {
		t.Error(err)
	}
	if len(enc) != 256 {
		t.Error("Wrong size of encrypted data")
	}
}

func TestBase64BlockEncode(t *testing.T) {
	toEncode := []byte("abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz")
	results := base64BlockEncode(toEncode)
	expected := []string{"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXphYmNkZWZnaGlqa2xtbm9wcXJz", "dHV2d3h5emFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6"}
	if !reflect.DeepEqual(results, expected) {
		t.Error("Results not matching")
	}
}

func TestKeyFromString(t *testing.T) {
	config := testConfig()
	_, err := keyFromString([]byte(config.KeyString))
	if err != nil {
		t.Error(err)
	}
}

func TestKeyFromFile(t *testing.T) {
	config := testConfig()
	_, err := keyFromFile(config.KeyPath)
	if err != nil {
		t.Error(err)
	}
}

func TestSplitWhitespace(t *testing.T) {
	str := "c   h   e   f"
	if !reflect.DeepEqual(splitWhitespace(str), []string{"c", "h", "e", "f"}) {
		t.Error("splitWhitespace slices not equal")
	}
}

func TestFilterQuotes(t *testing.T) {
	known := map[string]string{
		`'this`: "this",
		`this'`: "this",
		`"this`: "this",
		`this"`: "this",
	}

	for bad, good := range known {
		if filterQuotes(bad) != good {
			t.Error("filterQuotes didn't produce an expected string")
		}
	}
}
<|endoftext|>


<commit_before>package gosync

import (
    "crypto/md5"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "strings"
    "launchpad.net/goamz/aws"
    "launchpad.net/goamz/s3"
)

type SyncPair struct {
    Source string
    Target string
    Auth aws.Auth
}

func (s *SyncPair) Sync() bool {
    if s.validPair() {
        if validS3Url(s.Source) {
           s.syncS3ToDir()
           return true
        } else {
           s.syncDirToS3()
           return true
        }
    }
    fmt.Printf("Path not valid.")
    return false
}

func (s *SyncPair) syncDirToS3() bool {
    sourceFiles := loadLocalFiles(s.Source)
    targetFiles := loadS3Files(s.Target, s.Auth)

    region := aws.USEast
    s3 := s3.New(s.Auth, region)

    for k, _ := range sourceFiles {
        if targetFiles[k] != sourceFiles[k] {
            fmt.Printf("Syncing %s\n", k)
            s3url := S3Url{Url: s.Target}
            st := []string{s.Source, k}
            key := strings.Join(st, "/")
            bucket := s3.Bucket(s3url.Bucket())
            pt := strings.Join([]string{s.Target, k}, "/")
            Put(bucket, pt, key)
        } else {
            fmt.Printf("Not Syncing %s\n", k)
        }
    }
    return true
}

func (s *SyncPair) syncS3ToDir() bool {
    sourceFiles := loadS3Files(s.Source, s.Auth)
    targetFiles := loadLocalFiles(s.Target)
    fmt.Printf("Sources:\n")
    for k, _ := range sourceFiles { fmt.Printf("Key %s Value %s\n", k, sourceFiles[k]) }
    fmt.Printf("Targets:\n")
    for k, _ := range targetFiles { fmt.Printf("Key %s Value %s\n", k, targetFiles[k]) }
    return true
}

func loadS3Files(url string, auth aws.Auth) map[string]string {
    files := map[string]string{}
          s3url := S3Url{Url: url}
          key := s3url.Key()
          region := aws.USEast
          s := s3.New(auth, region)
          bucket := s.Bucket(s3url.Bucket())
          defer func() {
              if r := recover(); r != nil {
                  fmt.Printf("%v", r)
              }
          }()
          data, err := bucket.List(key, "", "", 0)
          if err != nil {
             panic(err.Error())
          }
          for i := range data.Contents {
            md5sum := data.Contents[i].ETag
            k := strings.TrimLeft(data.Contents[i].Key, url)
            files[k] = strings.Trim(md5sum, "\"")
          }
          return files
}

func loadLocalFiles(path string) map[string]string {
    files := map[string]string{}
    filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
        if !info.IsDir() {
            relativePath := strings.TrimLeft(filePath, path)

            buf, err := ioutil.ReadFile(filePath)
            if err != nil {
                panic(err)
            }

            hasher := md5.New()
            hasher.Write(buf)
            md5sum := fmt.Sprintf("%x", hasher.Sum(nil))
            files[relativePath] = md5sum
        }
        return nil
    })
    return files
}

func (s *SyncPair) validPair() bool {
     if pathExists(s.Source) == false && pathExists(s.Target) == false {
         return false
     }
     if validS3Url(s.Source) == false && validS3Url(s.Target) == false {
         return false
     }
     return true
}

func validS3Url(path string) bool {
    return strings.HasPrefix(path, "s3://")
}

func pathExists(path string) (bool) {
    _, err := os.Stat(path)
    if err == nil { return true }
    if os.IsNotExist(err) { return false }
    return false
}
<commit_msg>working on sync logic<commit_after>package gosync

import (
    "crypto/md5"
    "fmt"
    "io/ioutil"
    "os"
    "path/filepath"
    "strings"
    "launchpad.net/goamz/aws"
    "launchpad.net/goamz/s3"
)

type SyncPair struct {
    Source string
    Target string
    Auth aws.Auth
}

func (s *SyncPair) Sync() bool {
    if s.validPair() {
        if validS3Url(s.Source) {
           s.syncS3ToDir()
           return true
        } else {
           s.syncDirToS3()
           return true
        }
    }
    fmt.Printf("Path not valid.")
    return false
}

func (s *SyncPair) syncDirToS3() bool {
    sourceFiles := loadLocalFiles(s.Source)
    targetFiles := loadS3Files(s.Target, s.Auth)

    region := aws.USEast
    s3 := s3.New(s.Auth, region)
    s3url := S3Url{Url: s.Target}

    for file, _ := range sourceFiles {
fmt.Printf("%s\n", file)
fmt.Printf("%s\n", targetFiles)
fmt.Printf("%s\n", sourceFiles)
fmt.Printf("%s\n", targetFiles[file])
fmt.Printf("%s\n", sourceFiles[file])
        if targetFiles[file] != sourceFiles[file] {
            fmt.Printf("Syncing %s\n", file)
            filePath := strings.Join([]string{s.Source, file}, "/")
            bucket := s3.Bucket(s3url.Bucket())
            fmt.Printf("%s syncing to %s", filePath, file)
            Put(bucket, file, filePath)
        } else {
            fmt.Printf("Not Syncing %s\n", file)
        }
    }
    return true
}

func (s *SyncPair) syncS3ToDir() bool {
    sourceFiles := loadS3Files(s.Source, s.Auth)
    targetFiles := loadLocalFiles(s.Target)
    fmt.Printf("Sources:\n")
    for k, _ := range sourceFiles { fmt.Printf("Key %s Value %s\n", k, sourceFiles[k]) }
    fmt.Printf("Targets:\n")
    for k, _ := range targetFiles { fmt.Printf("Key %s Value %s\n", k, targetFiles[k]) }
    return true
}

func loadS3Files(url string, auth aws.Auth) map[string]string {
    files := map[string]string{}
          s3url := S3Url{Url: url}
          key := s3url.Key()
          region := aws.USEast
          s := s3.New(auth, region)
          bucket := s.Bucket(s3url.Bucket())
          defer func() {
              if r := recover(); r != nil {
                  fmt.Printf("%v", r)
              }
          }()
          data, err := bucket.List(key, "", "", 0)
          if err != nil {
             panic(err.Error())
          }
          for i := range data.Contents {
            md5sum := data.Contents[i].ETag
            k := strings.TrimLeft(data.Contents[i].Key, url)
            files[k] = strings.Trim(md5sum, "\"")
          }
          return files
}

func loadLocalFiles(path string) map[string]string {
    files := map[string]string{}
    filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error {
        if !info.IsDir() {
            relativePath := strings.TrimLeft(filePath, path)

            buf, err := ioutil.ReadFile(filePath)
            if err != nil {
                panic(err)
            }

            hasher := md5.New()
            hasher.Write(buf)
            md5sum := fmt.Sprintf("%x", hasher.Sum(nil))
            files[relativePath] = md5sum
        }
        return nil
    })
    return files
}

func (s *SyncPair) validPair() bool {
     if pathExists(s.Source) == false && pathExists(s.Target) == false {
         return false
     }
     if validS3Url(s.Source) == false && validS3Url(s.Target) == false {
         return false
     }
     return true
}

func validS3Url(path string) bool {
    return strings.HasPrefix(path, "s3://")
}

func pathExists(path string) (bool) {
    _, err := os.Stat(path)
    if err == nil { return true }
    if os.IsNotExist(err) { return false }
    return false
}
<|endoftext|>


<commit_before>package report

import (
	"bytes"
	"go/ast"
	"go/format"
	"go/token"
	"path/filepath"
	"strconv"
	"strings"

	"honnef.co/go/tools/analysis/facts"
	"honnef.co/go/tools/go/ast/astutil"

	"golang.org/x/tools/go/analysis"
)

type Options struct {
	ShortRange      bool
	FilterGenerated bool
	Fixes           []analysis.SuggestedFix
	Related         []analysis.RelatedInformation
}

type Option func(*Options)

func ShortRange() Option {
	return func(opts *Options) {
		opts.ShortRange = true
	}
}

func FilterGenerated() Option {
	return func(opts *Options) {
		opts.FilterGenerated = true
	}
}

func Fixes(fixes ...analysis.SuggestedFix) Option {
	return func(opts *Options) {
		opts.Fixes = append(opts.Fixes, fixes...)
	}
}

func Related(node Positioner, message string) Option {
	return func(opts *Options) {
		pos, end := getRange(node, opts.ShortRange)
		r := analysis.RelatedInformation{
			Pos:     pos,
			End:     end,
			Message: message,
		}
		opts.Related = append(opts.Related, r)
	}
}

type Positioner interface {
	Pos() token.Pos
}

type fullPositioner interface {
	Pos() token.Pos
	End() token.Pos
}

type sourcer interface {
	Source() ast.Node
}

// shortRange returns the position and end of the main component of an
// AST node. For nodes that have no body, the short range is identical
// to the node's Pos and End. For nodes that do have a body, the short
// range excludes the body.
func shortRange(node ast.Node) (pos, end token.Pos) {
	switch node := node.(type) {
	case *ast.File:
		return node.Pos(), node.Name.End()
	case *ast.CaseClause:
		return node.Pos(), node.Colon + 1
	case *ast.CommClause:
		return node.Pos(), node.Colon + 1
	case *ast.DeferStmt:
		return node.Pos(), node.Defer + token.Pos(len("defer"))
	case *ast.ExprStmt:
		return shortRange(node.X)
	case *ast.ForStmt:
		if node.Post != nil {
			return node.For, node.Post.End()
		} else if node.Cond != nil {
			return node.For, node.Cond.End()
		} else if node.Init != nil {
			// +1 to catch the semicolon, for gofmt'ed code
			return node.Pos(), node.Init.End() + 1
		} else {
			return node.Pos(), node.For + token.Pos(len("for"))
		}
	case *ast.FuncDecl:
		return node.Pos(), node.Type.End()
	case *ast.FuncLit:
		return node.Pos(), node.Type.End()
	case *ast.GoStmt:
		if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
			return node.Pos(), node.Go + token.Pos(len("go"))
		} else {
			return node.Pos(), node.End()
		}
	case *ast.IfStmt:
		return node.Pos(), node.Cond.End()
	case *ast.RangeStmt:
		return node.Pos(), node.X.End()
	case *ast.SelectStmt:
		return node.Pos(), node.Pos() + token.Pos(len("select"))
	case *ast.SwitchStmt:
		if node.Tag != nil {
			return node.Pos(), node.Tag.End()
		} else if node.Init != nil {
			// +1 to catch the semicolon, for gofmt'ed code
			return node.Pos(), node.Init.End() + 1
		} else {
			return node.Pos(), node.Pos() + token.Pos(len("switch"))
		}
	case *ast.TypeSwitchStmt:
		return node.Pos(), node.Assign.End()
	default:
		return node.Pos(), node.End()
	}
}

func getRange(node Positioner, short bool) (pos, end token.Pos) {
	switch node := node.(type) {
	case sourcer:
		s := node.Source()
		if short {
			return shortRange(s)
		}
		return s.Pos(), s.End()
	case fullPositioner:
		if short {
			return shortRange(node)
		}
		return node.Pos(), node.End()
	default:
		return node.Pos(), token.NoPos
	}
}

func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
	cfg := &Options{}
	for _, opt := range opts {
		opt(cfg)
	}

	file := DisplayPosition(pass.Fset, node.Pos()).Filename
	if cfg.FilterGenerated {
		m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
		if _, ok := m[file]; ok {
			return
		}
	}

	pos, end := getRange(node, cfg.ShortRange)
	d := analysis.Diagnostic{
		Pos:            pos,
		End:            end,
		Message:        message,
		SuggestedFixes: cfg.Fixes,
		Related:        cfg.Related,
	}
	pass.Report(d)
}

func Render(pass *analysis.Pass, x interface{}) string {
	var buf bytes.Buffer
	if err := format.Node(&buf, pass.Fset, x); err != nil {
		panic(err)
	}
	return buf.String()
}

func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
	var ss []string
	for _, arg := range args {
		ss = append(ss, Render(pass, arg))
	}
	return strings.Join(ss, ", ")
}

func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
	if p == token.NoPos {
		return token.Position{}
	}

	// Only use the adjusted position if it points to another Go file.
	// This means we'll point to the original file for cgo files, but
	// we won't point to a YACC grammar file.
	pos := fset.PositionFor(p, false)
	adjPos := fset.PositionFor(p, true)

	if filepath.Ext(adjPos.Filename) == ".go" {
		return adjPos
	}

	return pos
}

func Ordinal(n int) string {
	suffix := "th"
	if n < 10 || n > 20 {
		switch n % 10 {
		case 0:
			suffix = "th"
		case 1:
			suffix = "st"
		case 2:
			suffix = "nd"
		case 3:
			suffix = "rd"
		default:
			suffix = "th"
		}
	}

	return strconv.Itoa(n) + suffix
}
<commit_msg>analysis/report: better handling of sourcers with no node<commit_after>package report

import (
	"bytes"
	"fmt"
	"go/ast"
	"go/format"
	"go/token"
	"path/filepath"
	"strconv"
	"strings"

	"honnef.co/go/tools/analysis/facts"
	"honnef.co/go/tools/go/ast/astutil"

	"golang.org/x/tools/go/analysis"
)

type Options struct {
	ShortRange      bool
	FilterGenerated bool
	Fixes           []analysis.SuggestedFix
	Related         []analysis.RelatedInformation
}

type Option func(*Options)

func ShortRange() Option {
	return func(opts *Options) {
		opts.ShortRange = true
	}
}

func FilterGenerated() Option {
	return func(opts *Options) {
		opts.FilterGenerated = true
	}
}

func Fixes(fixes ...analysis.SuggestedFix) Option {
	return func(opts *Options) {
		opts.Fixes = append(opts.Fixes, fixes...)
	}
}

func Related(node Positioner, message string) Option {
	return func(opts *Options) {
		pos, end, ok := getRange(node, opts.ShortRange)
		if !ok {
			return
		}
		r := analysis.RelatedInformation{
			Pos:     pos,
			End:     end,
			Message: message,
		}
		opts.Related = append(opts.Related, r)
	}
}

type Positioner interface {
	Pos() token.Pos
}

type fullPositioner interface {
	Pos() token.Pos
	End() token.Pos
}

type sourcer interface {
	Source() ast.Node
}

// shortRange returns the position and end of the main component of an
// AST node. For nodes that have no body, the short range is identical
// to the node's Pos and End. For nodes that do have a body, the short
// range excludes the body.
func shortRange(node ast.Node) (pos, end token.Pos) {
	switch node := node.(type) {
	case *ast.File:
		return node.Pos(), node.Name.End()
	case *ast.CaseClause:
		return node.Pos(), node.Colon + 1
	case *ast.CommClause:
		return node.Pos(), node.Colon + 1
	case *ast.DeferStmt:
		return node.Pos(), node.Defer + token.Pos(len("defer"))
	case *ast.ExprStmt:
		return shortRange(node.X)
	case *ast.ForStmt:
		if node.Post != nil {
			return node.For, node.Post.End()
		} else if node.Cond != nil {
			return node.For, node.Cond.End()
		} else if node.Init != nil {
			// +1 to catch the semicolon, for gofmt'ed code
			return node.Pos(), node.Init.End() + 1
		} else {
			return node.Pos(), node.For + token.Pos(len("for"))
		}
	case *ast.FuncDecl:
		return node.Pos(), node.Type.End()
	case *ast.FuncLit:
		return node.Pos(), node.Type.End()
	case *ast.GoStmt:
		if _, ok := astutil.Unparen(node.Call.Fun).(*ast.FuncLit); ok {
			return node.Pos(), node.Go + token.Pos(len("go"))
		} else {
			return node.Pos(), node.End()
		}
	case *ast.IfStmt:
		return node.Pos(), node.Cond.End()
	case *ast.RangeStmt:
		return node.Pos(), node.X.End()
	case *ast.SelectStmt:
		return node.Pos(), node.Pos() + token.Pos(len("select"))
	case *ast.SwitchStmt:
		if node.Tag != nil {
			return node.Pos(), node.Tag.End()
		} else if node.Init != nil {
			// +1 to catch the semicolon, for gofmt'ed code
			return node.Pos(), node.Init.End() + 1
		} else {
			return node.Pos(), node.Pos() + token.Pos(len("switch"))
		}
	case *ast.TypeSwitchStmt:
		return node.Pos(), node.Assign.End()
	default:
		return node.Pos(), node.End()
	}
}

func getRange(node Positioner, short bool) (pos, end token.Pos, ok bool) {
	switch n := node.(type) {
	case sourcer:
		s := n.Source()
		if s == nil {
			return 0, 0, false
		}
		if short {
			p, e := shortRange(s)
			return p, e, true
		}
		return s.Pos(), s.End(), true
	case fullPositioner:
		if short {
			p, e := shortRange(n)
			return p, e, true
		}
		return n.Pos(), n.End(), true
	default:
		return n.Pos(), token.NoPos, true
	}
}

func Report(pass *analysis.Pass, node Positioner, message string, opts ...Option) {
	cfg := &Options{}
	for _, opt := range opts {
		opt(cfg)
	}

	file := DisplayPosition(pass.Fset, node.Pos()).Filename
	if cfg.FilterGenerated {
		m := pass.ResultOf[facts.Generated].(map[string]facts.Generator)
		if _, ok := m[file]; ok {
			return
		}
	}

	pos, end, ok := getRange(node, cfg.ShortRange)
	if !ok {
		panic(fmt.Sprintf("no valid position for reporting node %v", node))
	}
	d := analysis.Diagnostic{
		Pos:            pos,
		End:            end,
		Message:        message,
		SuggestedFixes: cfg.Fixes,
		Related:        cfg.Related,
	}
	pass.Report(d)
}

func Render(pass *analysis.Pass, x interface{}) string {
	var buf bytes.Buffer
	if err := format.Node(&buf, pass.Fset, x); err != nil {
		panic(err)
	}
	return buf.String()
}

func RenderArgs(pass *analysis.Pass, args []ast.Expr) string {
	var ss []string
	for _, arg := range args {
		ss = append(ss, Render(pass, arg))
	}
	return strings.Join(ss, ", ")
}

func DisplayPosition(fset *token.FileSet, p token.Pos) token.Position {
	if p == token.NoPos {
		return token.Position{}
	}

	// Only use the adjusted position if it points to another Go file.
	// This means we'll point to the original file for cgo files, but
	// we won't point to a YACC grammar file.
	pos := fset.PositionFor(p, false)
	adjPos := fset.PositionFor(p, true)

	if filepath.Ext(adjPos.Filename) == ".go" {
		return adjPos
	}

	return pos
}

func Ordinal(n int) string {
	suffix := "th"
	if n < 10 || n > 20 {
		switch n % 10 {
		case 0:
			suffix = "th"
		case 1:
			suffix = "st"
		case 2:
			suffix = "nd"
		case 3:
			suffix = "rd"
		default:
			suffix = "th"
		}
	}

	return strconv.Itoa(n) + suffix
}
<|endoftext|>


<commit_before>package http

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pierrre/imageserver"
	imageserver_source "github.com/pierrre/imageserver/source"
	"github.com/pierrre/imageserver/testdata"
)

var _ imageserver.Server = &Server{}

func TestGet(t *testing.T) {
	httpSrv := createTestHTTPServer(t)
	defer httpSrv.Close()
	params := imageserver.Params{
		imageserver_source.Param: createTestSource(httpSrv, testdata.MediumFileName),
	}
	srv := &Server{}
	im, err := srv.Get(params)
	if err != nil {
		t.Fatal(err)
	}
	if im == nil {
		t.Fatal("no image")
	}
	if im.Format != testdata.Medium.Format {
		t.Fatalf("unexpected image format: got \"%s\", want \"%s\"", im.Format, testdata.Medium.Format)
	}
	if len(im.Data) != len(testdata.Medium.Data) {
		t.Fatalf("unexpected image data length: got %d, want %d", len(im.Data), len(testdata.Medium.Data))
	}
}

func TestGetErrorNoSource(t *testing.T) {
	httpSrv := createTestHTTPServer(t)
	defer httpSrv.Close()
	params := imageserver.Params{}
	srv := &Server{}
	_, err := srv.Get(params)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

func TestGetErrorNewRequest(t *testing.T) {
	params := imageserver.Params{imageserver_source.Param: "%"}
	srv := &Server{}
	_, err := srv.Get(params)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

func TestGetErrorDoRequest(t *testing.T) {
	params := imageserver.Params{imageserver_source.Param: "http://localhost:123456"}
	srv := &Server{}
	_, err := srv.Get(params)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

func TestGetErrorNotFound(t *testing.T) {
	httpSrv := createTestHTTPServer(t)
	defer httpSrv.Close()
	source := createTestSource(httpSrv, testdata.MediumFileName)
	source += "foobar"
	params := imageserver.Params{imageserver_source.Param: source}
	srv := &Server{}
	_, err := srv.Get(params)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

type errorReadCloser struct{}

func (erc *errorReadCloser) Read(p []byte) (n int, err error) {
	return 0, fmt.Errorf("error")
}

func (erc *errorReadCloser) Close() error {
	return fmt.Errorf("error")
}

func TestParseResponseErrorData(t *testing.T) {
	response := &http.Response{
		StatusCode: http.StatusOK,
		Body:       &errorReadCloser{},
	}
	_, err := parseResponse(response)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

func createTestHTTPServer(t *testing.T) *httptest.Server {
	return httptest.NewServer(http.FileServer(http.Dir(testdata.Dir)))
}

func createTestSource(srv *httptest.Server, filename string) string {
	return fmt.Sprintf("http://%s/%s", srv.Listener.Addr(), filename)
}
<commit_msg>source/http: refactor tests: use test cases instead of functions<commit_after>package http

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pierrre/imageserver"
	imageserver_source "github.com/pierrre/imageserver/source"
	"github.com/pierrre/imageserver/testdata"
)

var _ imageserver.Server = &Server{}

func TestGet(t *testing.T) {
	srv := &Server{}
	httpSrv := createTestHTTPServer()
	defer httpSrv.Close()
	for _, tc := range []struct {
		name               string
		params             imageserver.Params
		expectedParamError string
		expectedImage      *imageserver.Image
	}{
		{
			name: "Normal",
			params: imageserver.Params{
				imageserver_source.Param: createTestSource(httpSrv, testdata.MediumFileName),
			},
			expectedImage: testdata.Medium,
		},
		{
			name:               "NoSource",
			params:             imageserver.Params{},
			expectedParamError: imageserver_source.Param,
		},
		{
			name: "InvalidURL",
			params: imageserver.Params{
				imageserver_source.Param: "%",
			},
			expectedParamError: imageserver_source.Param,
		},
		{
			name: "UnreachableURL",
			params: imageserver.Params{
				imageserver_source.Param: "http://localhost:123456",
			},
			expectedParamError: imageserver_source.Param,
		},
		{
			name: "NotFound",
			params: imageserver.Params{
				imageserver_source.Param: createTestSource(httpSrv, testdata.MediumFileName) + "foobar",
			},
			expectedParamError: imageserver_source.Param,
		},
	} {
		func() {
			t.Logf("test: %s", tc.name)
			im, err := srv.Get(tc.params)
			if err != nil {
				if err, ok := err.(*imageserver.ParamError); ok && err.Param == tc.expectedParamError {
					return
				}
				t.Fatal(err)
			}
			if tc.expectedParamError != "" {
				t.Fatal("no error")
			}
			if im == nil {
				t.Fatal("no image")
			}
			if im.Format != tc.expectedImage.Format {
				t.Fatalf("unexpected image format: got \"%s\", want \"%s\"", im.Format, tc.expectedImage.Format)
			}
			if !bytes.Equal(im.Data, tc.expectedImage.Data) {
				t.Fatal("data not equal")
			}
		}()
	}
}

type errorReadCloser struct{}

func (erc *errorReadCloser) Read(p []byte) (n int, err error) {
	return 0, fmt.Errorf("error")
}

func (erc *errorReadCloser) Close() error {
	return fmt.Errorf("error")
}

func TestParseResponseErrorData(t *testing.T) {
	response := &http.Response{
		StatusCode: http.StatusOK,
		Body:       &errorReadCloser{},
	}
	_, err := parseResponse(response)
	if err == nil {
		t.Fatal("no error")
	}
	if _, ok := err.(*imageserver.ParamError); !ok {
		t.Fatalf("unexpected error type: %T", err)
	}
}

func createTestHTTPServer() *httptest.Server {
	return httptest.NewServer(http.FileServer(http.Dir(testdata.Dir)))
}

func createTestSource(srv *httptest.Server, filename string) string {
	return fmt.Sprintf("http://%s/%s", srv.Listener.Addr(), filename)
}
<|endoftext|>


<commit_before>package worker

import (
	"encoding/json"
	"fmt"
	"time"

	gocontext "context"

	"github.com/Jeffail/tunny"
	"github.com/bitly/go-simplejson"
	"github.com/sirupsen/logrus"
	"github.com/streadway/amqp"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/context"
	"github.com/travis-ci/worker/metrics"
)

type amqpJob struct {
	conn            *amqp.Connection
	stateUpdatePool *tunny.Pool
	logWriterChan   *amqp.Channel
	delivery        amqp.Delivery
	payload         *JobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time
	finished        time.Time
	stateCount      uint
	withLogSharding bool
}

func (j *amqpJob) GoString() string {
	return fmt.Sprintf("&amqpJob{conn: %#v, delivery: %#v, payload: %#v, startAttributes: %#v}",
		j.conn, j.delivery, j.payload, j.startAttributes)
}

func (j *amqpJob) Payload() *JobPayload {
	return j.payload
}

func (j *amqpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *amqpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *amqpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx, time.Minute)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(ctx, FinishStateErrored)
}

func (j *amqpJob) Requeue(ctx gocontext.Context) error {
	context.LoggerFromContext(ctx).WithFields(
		logrus.Fields{
			"self":       "amqp_job",
			"job_id":     j.Payload().Job.ID,
			"repository": j.Payload().Repository.Slug,
		}).Info("requeueing job")

	metrics.Mark("worker.job.requeue")

	err := j.sendStateUpdate(ctx, "job:test:reset", "reset")
	if err != nil {
		return err
	}

	return j.delivery.Ack(false)
}

func (j *amqpJob) Received(ctx gocontext.Context) error {
	j.received = time.Now()

	if j.payload.Job.QueuedAt != nil {
		metrics.TimeSince("travis.worker.job.queue_time", *j.payload.Job.QueuedAt)
	}

	return j.sendStateUpdate(ctx, "job:test:receive", "received")
}

func (j *amqpJob) Started(ctx gocontext.Context) error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate(ctx, "job:test:start", "started")
}

func (j *amqpJob) Finish(ctx gocontext.Context, state FinishState) error {
	context.LoggerFromContext(ctx).WithFields(logrus.Fields{
		"state":      state,
		"self":       "amqp_job",
		"job_id":     j.Payload().Job.ID,
		"repository": j.Payload().Repository.Slug,
	}).Info("finishing job")

	j.finished = time.Now()
	if j.received.IsZero() {
		j.received = j.finished
	}

	if j.started.IsZero() {
		j.started = j.finished
	}

	metrics.Mark(fmt.Sprintf("travis.worker.job.finish.%s", state))
	metrics.Mark("travis.worker.job.finish")

	err := j.sendStateUpdate(ctx, "job:test:finish", string(state))
	if err != nil {
		return err
	}

	return j.delivery.Ack(false)
}

func (j *amqpJob) LogWriter(ctx gocontext.Context, defaultLogTimeout time.Duration) (LogWriter, error) {
	logTimeout := time.Duration(j.payload.Timeouts.LogSilence) * time.Second
	if logTimeout == 0 {
		logTimeout = defaultLogTimeout
	}

	return newAMQPLogWriter(ctx, j.logWriterChan, j.payload.Job.ID, logTimeout, j.withLogSharding)
}

func (j *amqpJob) createStateUpdateBody(ctx gocontext.Context, state string) map[string]interface{} {
	body := map[string]interface{}{
		"id":    j.Payload().Job.ID,
		"state": state,
		"meta": map[string]interface{}{
			"state_update_count": j.stateCount,
		},
	}

	if instanceID, ok := context.InstanceIDFromContext(ctx); ok {
		body["meta"].(map[string]interface{})["instance_id"] = instanceID
	}

	if j.Payload().Job.QueuedAt != nil {
		body["queued_at"] = j.Payload().Job.QueuedAt.UTC().Format(time.RFC3339)
	}
	if !j.received.IsZero() {
		body["received_at"] = j.received.UTC().Format(time.RFC3339)
	}
	if !j.started.IsZero() {
		body["started_at"] = j.started.UTC().Format(time.RFC3339)
	}
	if !j.finished.IsZero() {
		body["finished_at"] = j.finished.UTC().Format(time.RFC3339)
	}

	if j.Payload().Trace {
		body["trace"] = true
	}

	return body
}

func (j *amqpJob) sendStateUpdate(ctx gocontext.Context, event, state string) error {
	err := j.stateUpdatePool.Process(&amqpStateUpdatePayload{
		job:   j,
		ctx:   ctx,
		event: event,
		state: state,
		body:  j.createStateUpdateBody(ctx, state),
	})

	if err == nil {
		return nil
	}

	return err.(error)
}

func (j *amqpJob) SetupContext(ctx gocontext.Context) gocontext.Context { return ctx }

func (j *amqpJob) Name() string { return "amqp" }

type amqpStateUpdatePayload struct {
	job   *amqpJob
	ctx   gocontext.Context
	event string
	state string
	body  map[string]interface{}
}

type amqpStateUpdateWorker struct {
	stateUpdateChan *amqp.Channel
	ctx             gocontext.Context
	cancel          gocontext.CancelFunc
}

func (w *amqpStateUpdateWorker) Process(payload interface{}) interface{} {
	p := payload.(*amqpStateUpdatePayload)
	ctx, cancel := gocontext.WithCancel(p.ctx)

	w.ctx = ctx
	w.cancel = cancel

	return w.sendStateUpdate(p)
}

func (w *amqpStateUpdateWorker) BlockUntilReady() {
	// we do not need to perform any warm-up before processing jobs.
	// Process() will block for the duration of the job itself.
}

func (w *amqpStateUpdateWorker) Interrupt() {
	w.cancel()
}

func (w *amqpStateUpdateWorker) Terminate() {
	err := w.stateUpdateChan.Close()
	if err != nil {
		time.Sleep(time.Minute)
		logrus.WithFields(logrus.Fields{
			"self": "amqp_state_update_worker",
			"err":  err,
		}).Panic("timed out waiting for shutdown after amqp connection error")
	}
}

func (w *amqpStateUpdateWorker) sendStateUpdate(payload *amqpStateUpdatePayload) error {
	select {
	case <-w.ctx.Done():
		return w.ctx.Err()
	default:
	}

	payload.job.stateCount++

	bodyBytes, err := json.Marshal(payload.body)
	if err != nil {
		return err
	}

	return w.stateUpdateChan.Publish("", "reporting.jobs.builds", false, false, amqp.Publishing{
		ContentType:  "application/json",
		DeliveryMode: amqp.Persistent,
		Timestamp:    time.Now().UTC(),
		Type:         payload.event,
		Body:         bodyBytes,
	})
}
<commit_msg>log duration of job execution (for honeycomb)<commit_after>package worker

import (
	"encoding/json"
	"fmt"
	"time"

	gocontext "context"

	"github.com/Jeffail/tunny"
	"github.com/bitly/go-simplejson"
	"github.com/sirupsen/logrus"
	"github.com/streadway/amqp"
	"github.com/travis-ci/worker/backend"
	"github.com/travis-ci/worker/context"
	"github.com/travis-ci/worker/metrics"
)

type amqpJob struct {
	conn            *amqp.Connection
	stateUpdatePool *tunny.Pool
	logWriterChan   *amqp.Channel
	delivery        amqp.Delivery
	payload         *JobPayload
	rawPayload      *simplejson.Json
	startAttributes *backend.StartAttributes
	received        time.Time
	started         time.Time
	finished        time.Time
	stateCount      uint
	withLogSharding bool
}

func (j *amqpJob) GoString() string {
	return fmt.Sprintf("&amqpJob{conn: %#v, delivery: %#v, payload: %#v, startAttributes: %#v}",
		j.conn, j.delivery, j.payload, j.startAttributes)
}

func (j *amqpJob) Payload() *JobPayload {
	return j.payload
}

func (j *amqpJob) RawPayload() *simplejson.Json {
	return j.rawPayload
}

func (j *amqpJob) StartAttributes() *backend.StartAttributes {
	return j.startAttributes
}

func (j *amqpJob) Error(ctx gocontext.Context, errMessage string) error {
	log, err := j.LogWriter(ctx, time.Minute)
	if err != nil {
		return err
	}

	_, err = log.WriteAndClose([]byte(errMessage))
	if err != nil {
		return err
	}

	return j.Finish(ctx, FinishStateErrored)
}

func (j *amqpJob) Requeue(ctx gocontext.Context) error {
	context.LoggerFromContext(ctx).WithFields(
		logrus.Fields{
			"self":       "amqp_job",
			"job_id":     j.Payload().Job.ID,
			"repository": j.Payload().Repository.Slug,
		}).Info("requeueing job")

	metrics.Mark("worker.job.requeue")

	err := j.sendStateUpdate(ctx, "job:test:reset", "reset")
	if err != nil {
		return err
	}

	return j.delivery.Ack(false)
}

func (j *amqpJob) Received(ctx gocontext.Context) error {
	j.received = time.Now()

	if j.payload.Job.QueuedAt != nil {
		metrics.TimeSince("travis.worker.job.queue_time", *j.payload.Job.QueuedAt)
	}

	return j.sendStateUpdate(ctx, "job:test:receive", "received")
}

func (j *amqpJob) Started(ctx gocontext.Context) error {
	j.started = time.Now()

	metrics.TimeSince("travis.worker.job.start_time", j.received)

	return j.sendStateUpdate(ctx, "job:test:start", "started")
}

func (j *amqpJob) Finish(ctx gocontext.Context, state FinishState) error {
	context.LoggerFromContext(ctx).WithFields(logrus.Fields{
		"state":           state,
		"self":            "amqp_job",
		"job_id":          j.Payload().Job.ID,
		"repository":      j.Payload().Repository.Slug,
		"job_duration_ms": j.finished.Sub(j.started) * time.Millisecond,
	}).Info("finishing job")

	j.finished = time.Now()
	if j.received.IsZero() {
		j.received = j.finished
	}

	if j.started.IsZero() {
		j.started = j.finished
	}

	metrics.Mark(fmt.Sprintf("travis.worker.job.finish.%s", state))
	metrics.Mark("travis.worker.job.finish")

	err := j.sendStateUpdate(ctx, "job:test:finish", string(state))
	if err != nil {
		return err
	}

	return j.delivery.Ack(false)
}

func (j *amqpJob) LogWriter(ctx gocontext.Context, defaultLogTimeout time.Duration) (LogWriter, error) {
	logTimeout := time.Duration(j.payload.Timeouts.LogSilence) * time.Second
	if logTimeout == 0 {
		logTimeout = defaultLogTimeout
	}

	return newAMQPLogWriter(ctx, j.logWriterChan, j.payload.Job.ID, logTimeout, j.withLogSharding)
}

func (j *amqpJob) createStateUpdateBody(ctx gocontext.Context, state string) map[string]interface{} {
	body := map[string]interface{}{
		"id":    j.Payload().Job.ID,
		"state": state,
		"meta": map[string]interface{}{
			"state_update_count": j.stateCount,
		},
	}

	if instanceID, ok := context.InstanceIDFromContext(ctx); ok {
		body["meta"].(map[string]interface{})["instance_id"] = instanceID
	}

	if j.Payload().Job.QueuedAt != nil {
		body["queued_at"] = j.Payload().Job.QueuedAt.UTC().Format(time.RFC3339)
	}
	if !j.received.IsZero() {
		body["received_at"] = j.received.UTC().Format(time.RFC3339)
	}
	if !j.started.IsZero() {
		body["started_at"] = j.started.UTC().Format(time.RFC3339)
	}
	if !j.finished.IsZero() {
		body["finished_at"] = j.finished.UTC().Format(time.RFC3339)
	}

	if j.Payload().Trace {
		body["trace"] = true
	}

	return body
}

func (j *amqpJob) sendStateUpdate(ctx gocontext.Context, event, state string) error {
	err := j.stateUpdatePool.Process(&amqpStateUpdatePayload{
		job:   j,
		ctx:   ctx,
		event: event,
		state: state,
		body:  j.createStateUpdateBody(ctx, state),
	})

	if err == nil {
		return nil
	}

	return err.(error)
}

func (j *amqpJob) SetupContext(ctx gocontext.Context) gocontext.Context { return ctx }

func (j *amqpJob) Name() string { return "amqp" }

type amqpStateUpdatePayload struct {
	job   *amqpJob
	ctx   gocontext.Context
	event string
	state string
	body  map[string]interface{}
}

type amqpStateUpdateWorker struct {
	stateUpdateChan *amqp.Channel
	ctx             gocontext.Context
	cancel          gocontext.CancelFunc
}

func (w *amqpStateUpdateWorker) Process(payload interface{}) interface{} {
	p := payload.(*amqpStateUpdatePayload)
	ctx, cancel := gocontext.WithCancel(p.ctx)

	w.ctx = ctx
	w.cancel = cancel

	return w.sendStateUpdate(p)
}

func (w *amqpStateUpdateWorker) BlockUntilReady() {
	// we do not need to perform any warm-up before processing jobs.
	// Process() will block for the duration of the job itself.
}

func (w *amqpStateUpdateWorker) Interrupt() {
	w.cancel()
}

func (w *amqpStateUpdateWorker) Terminate() {
	err := w.stateUpdateChan.Close()
	if err != nil {
		time.Sleep(time.Minute)
		logrus.WithFields(logrus.Fields{
			"self": "amqp_state_update_worker",
			"err":  err,
		}).Panic("timed out waiting for shutdown after amqp connection error")
	}
}

func (w *amqpStateUpdateWorker) sendStateUpdate(payload *amqpStateUpdatePayload) error {
	select {
	case <-w.ctx.Done():
		return w.ctx.Err()
	default:
	}

	payload.job.stateCount++

	bodyBytes, err := json.Marshal(payload.body)
	if err != nil {
		return err
	}

	return w.stateUpdateChan.Publish("", "reporting.jobs.builds", false, false, amqp.Publishing{
		ContentType:  "application/json",
		DeliveryMode: amqp.Persistent,
		Timestamp:    time.Now().UTC(),
		Type:         payload.event,
		Body:         bodyBytes,
	})
}
<|endoftext|>


<commit_before>package bham

import (
	"fmt"
	"strings"
)

func (pt *protoTree) analyze() {
	pt.doAnalyze(0, len(pt.lineList)-1)
	pt.nodes = pt.currNodes
}

func (pt *protoTree) doAnalyze(currentIndex, finalIndex int) {
	for currentIndex <= finalIndex {
		if pt.err != nil {
			return
		}

		line := pt.lineList[currentIndex]

		switch {
		case line.accept("%.#"):
			currentIndex = pt.tagLike(currentIndex, finalIndex)
			continue
		case line.accept("=-"):
			currentIndex = pt.actionableLine(currentIndex, finalIndex)
			continue
		case line.accept(":"):
			for _, handler := range Filters {
				if line.content == handler.Trigger {
					currentIndex = pt.followHandler(currentIndex+1, finalIndex, handler)
					fmt.Println("handler is ", handler.Trigger)
					return
				}
			}
			pt.err = fmt.Errorf("Bad handler: %s", line.content)
			return
		case line.prefix("!!!"):
			pt.insertDoctype(line)
			currentIndex++
		default:
			pt.insertText(line)
			currentIndex++
		}
	}
}

func (pt *protoTree) insertDoctype(line templateLine) {
	doctype, ok := Doctypes[strings.TrimSpace(line.content[3:])]
	if ok {
		pt.insertRaw(doctype, line.indentation)
	} else {
		pt.err = fmt.Errorf("Bad doctype, details: '%s'", line.content)
	}
}

func (pt *protoTree) followHandler(startIndex, finalIndex int, handler FilterHandler) int {
	lines := make([]string, 0)
	index := startIndex
	base := pt.lineList[startIndex].indentation
	for index <= finalIndex && base <= pt.lineList[index].indentation {
		diff := base - pt.lineList[index].indentation
		lines = append(lines, pad(diff)+pt.lineList[index].content)
		index++
	}
	pt.insertFilter(
		strings.Join(lines, "\n"),
		pt.lineList[startIndex].indentation,
		handler,
	)

	return index
}

func pad(indent int) string {
	var output string
	for i := 0; i < indent; i++ {
		output = output + "  "
	}
	return output
}

func (pt *protoTree) actionableLine(startIndex, finalIndex int) int {
	currentIndex := startIndex + 1
	var endIndex int
	if pt.lineList[startIndex].blockParameter() {
		for currentIndex <= finalIndex && pt.lineList[startIndex].indentation < pt.lineList[currentIndex].indentation {
			currentIndex++
		}
		parentNodes := pt.currNodes
		pt.currNodes = []protoNode{}
		pt.doAnalyze(startIndex+1, currentIndex-1)
		primaryNodes, secondaryNodes := pt.currNodes, []protoNode{}

		if pt.lineList[startIndex].mightHaveElse() && currentIndex < finalIndex {
			if pt.lineList[currentIndex].isElse() {
				endIndex = currentIndex
				currentIndex++
				for pt.lineList[endIndex].indentation < pt.lineList[currentIndex].indentation && currentIndex < finalIndex {
					currentIndex++
				}
				pt.currNodes = []protoNode{}
				pt.doAnalyze(endIndex+1, currentIndex)
				secondaryNodes = pt.currNodes
				currentIndex++
			}
		}
		pt.currNodes = parentNodes
		switch {
		case pt.lineList[startIndex].isIf():
			pt.insertIf(
				pt.lineList[startIndex].after("-=").without("if ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isUnless():
			pt.insertIf(
				pt.lineList[startIndex].after("-=").without("unless ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isRange():
			pt.insertRange(
				pt.lineList[startIndex].after("-=").without("range ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isWith():
			pt.insertWith(
				pt.lineList[startIndex].after("-=").without("with ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
			)
		}
	} else {
		pt.insertExecutable(
			pt.lineList[startIndex].after("-=").String(),
			pt.lineList[startIndex].indentation,
		)
	}

	return currentIndex
}

func (pt *protoTree) tagLike(currentIndex, finalIndex int) int {
	if finalIndex == currentIndex || pt.lineList[currentIndex+1].indentation <= pt.lineList[currentIndex].indentation {
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTag,
			content:    pt.lineList[currentIndex].content,
		})
		return currentIndex + 1
	} else {
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTagOpen,
			content:    pt.lineList[currentIndex].content,
		})
		tagIndex := currentIndex + 1
		for tagIndex < finalIndex && pt.lineList[tagIndex].indentation > pt.lineList[currentIndex].indentation {
			tagIndex++
		}
		if pt.lineList[tagIndex].indentation <= pt.lineList[currentIndex].indentation {
			tagIndex--
		}
		pt.doAnalyze(currentIndex+1, tagIndex)
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTagClose,
			content:    pt.lineList[currentIndex].content,
		})
		return tagIndex + 1
	}
}
<commit_msg>Remove test code<commit_after>package bham

import (
	"fmt"
	"strings"
)

func (pt *protoTree) analyze() {
	pt.doAnalyze(0, len(pt.lineList)-1)
	pt.nodes = pt.currNodes
}

func (pt *protoTree) doAnalyze(currentIndex, finalIndex int) {
	for currentIndex <= finalIndex {
		if pt.err != nil {
			return
		}

		line := pt.lineList[currentIndex]

		switch {
		case line.accept("%.#"):
			currentIndex = pt.tagLike(currentIndex, finalIndex)
			continue
		case line.accept("=-"):
			currentIndex = pt.actionableLine(currentIndex, finalIndex)
			continue
		case line.accept(":"):
			for _, handler := range Filters {
				if line.content == handler.Trigger {
					currentIndex = pt.followHandler(currentIndex+1, finalIndex, handler)
					return
				}
			}
			pt.err = fmt.Errorf("Bad handler: %s", line.content)
			return
		case line.prefix("!!!"):
			pt.insertDoctype(line)
			currentIndex++
		default:
			pt.insertText(line)
			currentIndex++
		}
	}
}

func (pt *protoTree) insertDoctype(line templateLine) {
	doctype, ok := Doctypes[strings.TrimSpace(line.content[3:])]
	if ok {
		pt.insertRaw(doctype, line.indentation)
	} else {
		pt.err = fmt.Errorf("Bad doctype, details: '%s'", line.content)
	}
}

func (pt *protoTree) followHandler(startIndex, finalIndex int, handler FilterHandler) int {
	lines := make([]string, 0)
	index := startIndex
	base := pt.lineList[startIndex].indentation
	for index <= finalIndex && base <= pt.lineList[index].indentation {
		diff := base - pt.lineList[index].indentation
		lines = append(lines, pad(diff)+pt.lineList[index].content)
		index++
	}
	pt.insertFilter(
		strings.Join(lines, "\n"),
		pt.lineList[startIndex].indentation,
		handler,
	)

	return index
}

func pad(indent int) string {
	var output string
	for i := 0; i < indent; i++ {
		output = output + "  "
	}
	return output
}

func (pt *protoTree) actionableLine(startIndex, finalIndex int) int {
	currentIndex := startIndex + 1
	var endIndex int
	if pt.lineList[startIndex].blockParameter() {
		for currentIndex <= finalIndex && pt.lineList[startIndex].indentation < pt.lineList[currentIndex].indentation {
			currentIndex++
		}
		parentNodes := pt.currNodes
		pt.currNodes = []protoNode{}
		pt.doAnalyze(startIndex+1, currentIndex-1)
		primaryNodes, secondaryNodes := pt.currNodes, []protoNode{}

		if pt.lineList[startIndex].mightHaveElse() && currentIndex < finalIndex {
			if pt.lineList[currentIndex].isElse() {
				endIndex = currentIndex
				currentIndex++
				for pt.lineList[endIndex].indentation < pt.lineList[currentIndex].indentation && currentIndex < finalIndex {
					currentIndex++
				}
				pt.currNodes = []protoNode{}
				pt.doAnalyze(endIndex+1, currentIndex)
				secondaryNodes = pt.currNodes
				currentIndex++
			}
		}
		pt.currNodes = parentNodes
		switch {
		case pt.lineList[startIndex].isIf():
			pt.insertIf(
				pt.lineList[startIndex].after("-=").without("if ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isUnless():
			pt.insertIf(
				pt.lineList[startIndex].after("-=").without("unless ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isRange():
			pt.insertRange(
				pt.lineList[startIndex].after("-=").without("range ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
				secondaryNodes,
			)
		case pt.lineList[startIndex].isWith():
			pt.insertWith(
				pt.lineList[startIndex].after("-=").without("with ").String(),
				pt.lineList[startIndex].indentation,
				primaryNodes,
			)
		}
	} else {
		pt.insertExecutable(
			pt.lineList[startIndex].after("-=").String(),
			pt.lineList[startIndex].indentation,
		)
	}

	return currentIndex
}

func (pt *protoTree) tagLike(currentIndex, finalIndex int) int {
	if finalIndex == currentIndex || pt.lineList[currentIndex+1].indentation <= pt.lineList[currentIndex].indentation {
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTag,
			content:    pt.lineList[currentIndex].content,
		})
		return currentIndex + 1
	} else {
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTagOpen,
			content:    pt.lineList[currentIndex].content,
		})
		tagIndex := currentIndex + 1
		for tagIndex < finalIndex && pt.lineList[tagIndex].indentation > pt.lineList[currentIndex].indentation {
			tagIndex++
		}
		if pt.lineList[tagIndex].indentation <= pt.lineList[currentIndex].indentation {
			tagIndex--
		}
		pt.doAnalyze(currentIndex+1, tagIndex)
		pt.currNodes = append(pt.currNodes, protoNode{
			level:      pt.lineList[currentIndex].indentation,
			identifier: identTagClose,
			content:    pt.lineList[currentIndex].content,
		})
		return tagIndex + 1
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	//"github.com/dcu/onetouch-ssh"
	"fmt"
	"github.com/cheggaaa/pb"
	"github.com/dcu/go-authy"
	"github.com/mgutz/ansi"
	"io/ioutil"
	"net/url"
	"os"
	"strings"
	"time"
)

var (
	// StatusPending is set when the request is pending.
	StatusPending = "pending"

	// StatusApproved is set when the request is approved.
	StatusApproved = "approved"

	// StatusDenied is set when the request is denied.
	StatusDenied = "denied"

	// StatusFailed is set when the request is failed.
	StatusFailed = "failed"
)

// ApprovalRequest is the approval request response.
type ApprovalRequest struct {
	Status   string `json:"status"`
	UUID     string `json:"uuid"`
	Notified bool   `json:"notified"`

	api     *authy.Authy
	bar     *pb.ProgressBar
	showBar bool
}

func buildParams() url.Values {
	hostname := runCommand("hostname")
	sshConnection := strings.Split(os.Getenv("SSH_CONNECTION"), " ")
	clientIP := ""
	serverIP := ""

	if len(sshConnection) > 1 {
		clientIP = formatIPAndLocation(sshConnection[0])
	}

	if len(sshConnection) > 2 {
		serverIP = formatIPAndLocation(sshConnection[2])
	}

	params := url.Values{
		"details[Type]":      {"SSH Server"},
		"details[Server IP]": {serverIP},
		"details[User IP]":   {clientIP},
		"details[User]":      {os.Getenv("USER")},
		"logos[][res]":       {"default"},
		"logos[][url]":       {"http://authy-assets-dev.s3.amazonaws.com/authenticator/ipad/logo/high/liberty_bank@2x.png"},
	}
	if command := os.Getenv("SSH_ORIGINAL_COMMAND"); command != "" {
		typ, repo := parseGitCommand(command)
		if typ != "" {
			params.Add("message", fmt.Sprintf("git %s on %s", typ, hostname))
			params.Add("details[Repository]", repo)
		} else {
			params.Add("message", fmt.Sprintf("You are executing command on %s", hostname))
			params.Add("details[Command]", command)
		}
	} else {
		params.Add("message", fmt.Sprintf("You are login to %s", hostname))
	}

	return params
}

// NewApprovalRequest creates a new approval request.
func NewApprovalRequest(api *authy.Authy, authyID int) (*ApprovalRequest, error) {
	params := buildParams()
	path := fmt.Sprintf(`/onetouch/json/users/%d/approval_requests`, authyID)

	response, err := api.DoRequest("POST", path, params)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	jsonResponse := struct {
		Success         bool             `json:"success"`
		ApprovalRequest *ApprovalRequest `json:"approval_request"`
	}{}

	err = json.Unmarshal(body, &jsonResponse)
	if err != nil {
		return nil, err
	}

	approvalRequest := jsonResponse.ApprovalRequest
	approvalRequest.api = api
	approvalRequest.showBar = isInteractiveConnection()
	return approvalRequest, nil
}

// CheckStatus returns the status of the request.
func (approvalRequest *ApprovalRequest) CheckStatus(timeout time.Duration) string {
	showBar := isInteractiveConnection()
	timeWaited := 0 * time.Second
	interval := 2 * time.Second

	status := StatusPending
	approvalRequest.bar = pb.New(int(timeout / interval))
	approvalRequest.setStatus(status)

	if showBar {
		approvalRequest.bar.Start()
	}

	for timeWaited < timeout {
		status = approvalRequest.requestStatus()
		approvalRequest.setStatus(status)
		if status != StatusPending {
			break
		}

		time.Sleep(interval)
		timeWaited += interval

		if showBar {
			approvalRequest.bar.Increment()
		}
	}

	if showBar {
		approvalRequest.bar.FinishPrint("")
	}

	return status
}

func (approvalRequest *ApprovalRequest) setStatus(status string) {
	if approvalRequest.showBar {
		approvalRequest.bar.Prefix(formatForStatus(status))
	}
}

func formatForStatus(status string) string {
	switch status {
	case StatusPending:
		{
			status = ansi.Color(status, "yellow+h")
		}
	case StatusApproved:
		{
			status = ansi.Color(status, "green+h")
		}
	case StatusFailed:
		{
			status = ansi.Color(status, "red+h")
		}
	case StatusDenied:
		{
			status = ansi.Color(status, "red+h")
		}
	}
	return fmt.Sprintf("[%s] ", status)
}

func (approvalRequest *ApprovalRequest) requestStatus() string {
	response, err := approvalRequest.api.DoRequest("GET", fmt.Sprintf("/onetouch/json/approval_requests/%s", approvalRequest.UUID), url.Values{})

	if err != nil {
		return StatusFailed
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return StatusFailed
	}

	jsonResponse := struct {
		Success         bool             `json:"success"`
		ApprovalRequest *ApprovalRequest `json:"approval_request"`
	}{}
	err = json.Unmarshal(body, &jsonResponse)
	if err != nil {
		return StatusFailed
	}

	status := jsonResponse.ApprovalRequest.Status
	return status
}
<commit_msg>Make use of the default logo for the transaction.<commit_after>package main

import (
	"encoding/json"
	//"github.com/dcu/onetouch-ssh"
	"fmt"
	"github.com/cheggaaa/pb"
	"github.com/dcu/go-authy"
	"github.com/mgutz/ansi"
	"io/ioutil"
	"net/url"
	"os"
	"strings"
	"time"
)

var (
	// StatusPending is set when the request is pending.
	StatusPending = "pending"

	// StatusApproved is set when the request is approved.
	StatusApproved = "approved"

	// StatusDenied is set when the request is denied.
	StatusDenied = "denied"

	// StatusFailed is set when the request is failed.
	StatusFailed = "failed"
)

// ApprovalRequest is the approval request response.
type ApprovalRequest struct {
	Status   string `json:"status"`
	UUID     string `json:"uuid"`
	Notified bool   `json:"notified"`

	api     *authy.Authy
	bar     *pb.ProgressBar
	showBar bool
}

func buildParams() url.Values {
	hostname := runCommand("hostname")
	sshConnection := strings.Split(os.Getenv("SSH_CONNECTION"), " ")
	clientIP := ""
	serverIP := ""

	if len(sshConnection) > 1 {
		clientIP = formatIPAndLocation(sshConnection[0])
	}

	if len(sshConnection) > 2 {
		serverIP = formatIPAndLocation(sshConnection[2])
	}

	params := url.Values{
		"details[Type]":      {"SSH Server"},
		"details[Server IP]": {serverIP},
		"details[User IP]":   {clientIP},
		"details[User]":      {os.Getenv("USER")},
		"logos[][res]":       {"default"},
		"logos[][url]":       {"https://s3.amazonaws.com/authy-assets/authenticator/android/logo/high/authenticator.png"},
	}
	if command := os.Getenv("SSH_ORIGINAL_COMMAND"); command != "" {
		typ, repo := parseGitCommand(command)
		if typ != "" {
			params.Add("message", fmt.Sprintf("git %s on %s", typ, hostname))
			params.Add("details[Repository]", repo)
		} else {
			params.Add("message", fmt.Sprintf("You are executing command on %s", hostname))
			params.Add("details[Command]", command)
		}
	} else {
		params.Add("message", fmt.Sprintf("You are login to %s", hostname))
	}

	return params
}

// NewApprovalRequest creates a new approval request.
func NewApprovalRequest(api *authy.Authy, authyID int) (*ApprovalRequest, error) {
	params := buildParams()
	path := fmt.Sprintf(`/onetouch/json/users/%d/approval_requests`, authyID)

	response, err := api.DoRequest("POST", path, params)
	if err != nil {
		return nil, err
	}

	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}

	jsonResponse := struct {
		Success         bool             `json:"success"`
		ApprovalRequest *ApprovalRequest `json:"approval_request"`
	}{}

	err = json.Unmarshal(body, &jsonResponse)
	if err != nil {
		return nil, err
	}

	approvalRequest := jsonResponse.ApprovalRequest
	approvalRequest.api = api
	approvalRequest.showBar = isInteractiveConnection()
	return approvalRequest, nil
}

// CheckStatus returns the status of the request.
func (approvalRequest *ApprovalRequest) CheckStatus(timeout time.Duration) string {
	showBar := isInteractiveConnection()
	timeWaited := 0 * time.Second
	interval := 2 * time.Second

	status := StatusPending
	approvalRequest.bar = pb.New(int(timeout / interval))
	approvalRequest.setStatus(status)

	if showBar {
		approvalRequest.bar.Start()
	}

	for timeWaited < timeout {
		status = approvalRequest.requestStatus()
		approvalRequest.setStatus(status)
		if status != StatusPending {
			break
		}

		time.Sleep(interval)
		timeWaited += interval

		if showBar {
			approvalRequest.bar.Increment()
		}
	}

	if showBar {
		approvalRequest.bar.FinishPrint("")
	}

	return status
}

func (approvalRequest *ApprovalRequest) setStatus(status string) {
	if approvalRequest.showBar {
		approvalRequest.bar.Prefix(formatForStatus(status))
	}
}

func formatForStatus(status string) string {
	switch status {
	case StatusPending:
		{
			status = ansi.Color(status, "yellow+h")
		}
	case StatusApproved:
		{
			status = ansi.Color(status, "green+h")
		}
	case StatusFailed:
		{
			status = ansi.Color(status, "red+h")
		}
	case StatusDenied:
		{
			status = ansi.Color(status, "red+h")
		}
	}
	return fmt.Sprintf("[%s] ", status)
}

func (approvalRequest *ApprovalRequest) requestStatus() string {
	response, err := approvalRequest.api.DoRequest("GET", fmt.Sprintf("/onetouch/json/approval_requests/%s", approvalRequest.UUID), url.Values{})

	if err != nil {
		return StatusFailed
	}
	defer response.Body.Close()
	body, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return StatusFailed
	}

	jsonResponse := struct {
		Success         bool             `json:"success"`
		ApprovalRequest *ApprovalRequest `json:"approval_request"`
	}{}
	err = json.Unmarshal(body, &jsonResponse)
	if err != nil {
		return StatusFailed
	}

	status := jsonResponse.ApprovalRequest.Status
	return status
}
<|endoftext|>


<commit_before>package formspec

import (
	"errors"
	"fmt"
)

type Result struct {
	Ok     bool
	Errors []*Error `json:"errors"`
}

func NewOkResult() *Result {
	r := &Result{}
	r.Ok = true

	return r
}

func NewNgResult() *Result {
	r := &Result{}
	r.Ok = false

	return r
}

type Error struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

func NewError(field, message string) *Error {
	return &Error{Field: field, Message: message}
}

func (e *Error) Error() string {
	return e.Message
}

// ----------------------------------------------------------------------------
// Formspec
// ----------------------------------------------------------------------------

type Form interface {
	FormValue(string) string
}

func New() *Formspec {
	return &Formspec{}
}

type Formspec struct {
	Rules []*Rule
}

func (f *Formspec) Rule(field string, ruleFunc RuleFunc) *Rule {
	rule := &Rule{Field: field, RuleFunc: ruleFunc}
	f.Rules = append(f.Rules, rule)
	return rule
}

func (f *Formspec) Validate(form Form) *Result {
	r := NewOkResult()

	for _, rule := range f.Rules {
		err := rule.Call(form)

		if err != nil {
			r.Ok = false
			r.Errors = append(r.Errors, NewError(rule.Field, err.Error()))
		}
	}

	return r
}

func (f *Formspec) Clone() *Formspec {
	clone := &Formspec{}

	for _, rule := range f.Rules {
		clone.Rules = append(clone.Rules, rule.clone())
	}

	return clone
}

// ----------------------------------------------------------------------------
// Rule
// ----------------------------------------------------------------------------

type FilterFunc func(string) string
type RuleFunc func(value string, f Form) error

type Rule struct {
	Field       string
	RuleFunc    RuleFunc
	FilterFuncs []FilterFunc
	allowBlank  bool

	// This is used prior to Rule.message.
	fullMessage string
	// This is used prior to error message that is returned from Rule.RuleFunc.
	message string
}

func (r *Rule) AllowBlank() *Rule {
	r.allowBlank = true
	return r
}

// If you override error message. Use following funcs `FullMessage()/Message()`.

// FullMessage sets Rule.fullMessage.
func (r *Rule) FullMessage(m string) *Rule {
	r.fullMessage = m
	return r
}

// Message sets Rule.message.
func (r *Rule) Message(m string) *Rule {
	r.message = m
	return r
}

func (r *Rule) Filter(filterFunc FilterFunc) *Rule {
	r.FilterFuncs = append(r.FilterFuncs, filterFunc)
	return r
}

func (r *Rule) Call(f Form) error {
	v := f.FormValue(r.Field)

	// Filter value
	if len(r.FilterFuncs) > 0 {
		for _, filterFunc := range r.FilterFuncs {
			v = filterFunc(v)
		}
	}

	// If rule.allowblank is true, all rule returns no error when value is blank.
	if v == "" && r.allowBlank {
		return nil
	}

	err := r.RuleFunc(v, f)

	if err != nil {
		if r.fullMessage != "" {
			return errors.New(r.fullMessage)
		}

		if r.message != "" {
			return fmt.Errorf("%s %s", r.Field, r.message)
		}

		return fmt.Errorf("%s %s", r.Field, err.Error())
	}

	return nil
}

func (r *Rule) clone() *Rule {
	return &Rule{
		Field:       r.Field,
		RuleFunc:    r.RuleFunc,
		FilterFuncs: r.FilterFuncs,
		allowBlank:  r.allowBlank,
		message:     r.message,
		fullMessage: r.fullMessage,
	}
}
<commit_msg>Don't show formspec.Result.Ok in json by default.<commit_after>package formspec

import (
	"errors"
	"fmt"
)

type Result struct {
	Ok     bool     `json:"-"`
	Errors []*Error `json:"errors"`
}

func NewOkResult() *Result {
	r := &Result{}
	r.Ok = true

	return r
}

func NewNgResult() *Result {
	r := &Result{}
	r.Ok = false

	return r
}

type Error struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

func NewError(field, message string) *Error {
	return &Error{Field: field, Message: message}
}

func (e *Error) Error() string {
	return e.Message
}

// ----------------------------------------------------------------------------
// Formspec
// ----------------------------------------------------------------------------

type Form interface {
	FormValue(string) string
}

func New() *Formspec {
	return &Formspec{}
}

type Formspec struct {
	Rules []*Rule
}

func (f *Formspec) Rule(field string, ruleFunc RuleFunc) *Rule {
	rule := &Rule{Field: field, RuleFunc: ruleFunc}
	f.Rules = append(f.Rules, rule)
	return rule
}

func (f *Formspec) Validate(form Form) *Result {
	r := NewOkResult()

	for _, rule := range f.Rules {
		err := rule.Call(form)

		if err != nil {
			r.Ok = false
			r.Errors = append(r.Errors, NewError(rule.Field, err.Error()))
		}
	}

	return r
}

func (f *Formspec) Clone() *Formspec {
	clone := &Formspec{}

	for _, rule := range f.Rules {
		clone.Rules = append(clone.Rules, rule.clone())
	}

	return clone
}

// ----------------------------------------------------------------------------
// Rule
// ----------------------------------------------------------------------------

type FilterFunc func(string) string
type RuleFunc func(value string, f Form) error

type Rule struct {
	Field       string
	RuleFunc    RuleFunc
	FilterFuncs []FilterFunc
	allowBlank  bool

	// This is used prior to Rule.message.
	fullMessage string
	// This is used prior to error message that is returned from Rule.RuleFunc.
	message string
}

func (r *Rule) AllowBlank() *Rule {
	r.allowBlank = true
	return r
}

// If you override error message. Use following funcs `FullMessage()/Message()`.

// FullMessage sets Rule.fullMessage.
func (r *Rule) FullMessage(m string) *Rule {
	r.fullMessage = m
	return r
}

// Message sets Rule.message.
func (r *Rule) Message(m string) *Rule {
	r.message = m
	return r
}

func (r *Rule) Filter(filterFunc FilterFunc) *Rule {
	r.FilterFuncs = append(r.FilterFuncs, filterFunc)
	return r
}

func (r *Rule) Call(f Form) error {
	v := f.FormValue(r.Field)

	// Filter value
	if len(r.FilterFuncs) > 0 {
		for _, filterFunc := range r.FilterFuncs {
			v = filterFunc(v)
		}
	}

	// If rule.allowblank is true, all rule returns no error when value is blank.
	if v == "" && r.allowBlank {
		return nil
	}

	err := r.RuleFunc(v, f)

	if err != nil {
		if r.fullMessage != "" {
			return errors.New(r.fullMessage)
		}

		if r.message != "" {
			return fmt.Errorf("%s %s", r.Field, r.message)
		}

		return fmt.Errorf("%s %s", r.Field, err.Error())
	}

	return nil
}

func (r *Rule) clone() *Rule {
	return &Rule{
		Field:       r.Field,
		RuleFunc:    r.RuleFunc,
		FilterFuncs: r.FilterFuncs,
		allowBlank:  r.allowBlank,
		message:     r.message,
		fullMessage: r.fullMessage,
	}
}
<|endoftext|>


<commit_before>package api

import (
	"encoding/json"
	"errors"
	"github.com/PaluMacil/dwn/configuration"
	"github.com/PaluMacil/dwn/database"
	"github.com/PaluMacil/dwn/database/store"
	"github.com/PaluMacil/dwn/module/core"
	"github.com/PaluMacil/dwn/webserver/errs"
	"net/http"
	"time"
)

// DELETE /api/core/email?userID=123&email=blah@example.com
func deleteEmailHandler(
	db *database.Database,
	config configuration.Configuration,
	cur core.Current,
	vars map[string]string,
	w http.ResponseWriter,
	r *http.Request,
) error {
	var request ModifyEmailRecordRequest
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		return err
	}

	// Must have permission to edit users unless editing oneself
	if err := cur.Can(core.PermissionEditUserInfo); err != nil && cur.User.ID != request.UserID {
		return err
	}
	user, err := db.Users.Get(request.UserID)
	if db.IsKeyNotFoundErr(err) {
		return errs.StatusNotFound
	} else if err != nil {
		return err
	}
	if request.Email == user.PrimaryEmail {
		return errs.StatusError{http.StatusBadRequest, errors.New("cannot delete primary email")}
	}
	updatedEmailList, err := deleteEmail(user.Emails, request.Email)
	if err != nil {
		return err
	}
	user.Emails = updatedEmailList

	return db.Users.Set(user)
}

func deleteEmail(emails []core.Email, email string) ([]core.Email, error) {
	var remainingVerifiedCount int
	var emailRecordForRemoval core.Email
	for i, record := range emails {
		if record.Email == email {
			emails = emails[:i+copy(emails[i:], emails[i+1:])]
			emailRecordForRemoval = record
		} else if record.Verified {
			// count number of verified emails
			remainingVerifiedCount++
		}
	}

	// you can always remove emails that are not verified
	if !emailRecordForRemoval.Verified {
		return emails, nil
	}
	// if this will eliminate the last verified email, you can't delete it
	if remainingVerifiedCount == 0 {
		return nil, errs.StatusError{http.StatusBadRequest, errors.New("cannot delete last verified email")}
	}

	return emails, nil
}

// POST /api/core/email?userID=123&email=blah@example.com&action=something
func emailActionHandler(
	db *database.Database,
	config configuration.Configuration,
	cur core.Current,
	vars map[string]string,
	w http.ResponseWriter,
	r *http.Request,
) error {
	var request ModifyEmailRecordRequest
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		return err
	}
	action := vars["action"]
	// Must have permission to edit users unless editing oneself
	if err := cur.Can(core.PermissionEditUserInfo); err != nil && cur.User.ID != request.UserID {
		return err
	}
	user, err := db.Users.Get(request.UserID)
	if db.IsKeyNotFoundErr(err) {
		return errs.StatusNotFound
	} else if err != nil {
		return err
	}

	// process actions
	switch action {
	case "setPrimary":
		if !verifiedEmailExists(user.Emails, request.Email) {
			return errs.StatusError{http.StatusBadRequest, errors.New("primary email must be verified")}
		}
		user.PrimaryEmail = request.Email
		user.ModifiedDate = time.Now()
	case "addEmail":
		exists, err := db.Users.EmailExists(request.Email)
		if err != nil {
			return err
		}
		if exists {
			return errs.StatusError{http.StatusBadRequest, errors.New("email already in use")}
		}
		// TODO: set verification code
		// TODO: send email, once implemented
		record := core.Email{
			Email:                request.Email,
			Verified:             false,
			VerifiedDate:         time.Time{},
			VerificationCode:     "",
			VerificationCodeDate: time.Time{},
		}
		user.Emails = append(user.Emails, record)
	case "resendVerificationMessage":
		// TODO: update code and send email, once implemented
	}
	if err = db.Users.Set(user); err != nil {
		return err
	}

	return json.NewEncoder(w).Encode(user.Info())
}

func verifiedEmailExists(emails []core.Email, email string) bool {
	for _, record := range emails {
		if record.Email == email && record.Verified {
			return true
		}
	}
	return false
}

type ModifyEmailRecordRequest struct {
	Email  string         `json:"email"`
	UserID store.Identity `json:"userID"`
}
<commit_msg>email api fixes<commit_after>package api

import (
	"encoding/json"
	"errors"
	"github.com/PaluMacil/dwn/configuration"
	"github.com/PaluMacil/dwn/database"
	"github.com/PaluMacil/dwn/database/store"
	"github.com/PaluMacil/dwn/module/core"
	"github.com/PaluMacil/dwn/webserver/errs"
	"net/http"
	"time"
)

// DELETE /api/core/email
func deleteEmailHandler(
	db *database.Database,
	config configuration.Configuration,
	cur core.Current,
	vars map[string]string,
	w http.ResponseWriter,
	r *http.Request,
) error {
	var request ModifyEmailRecordRequest
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		return err
	}

	// Must have permission to edit users unless editing oneself
	if err := cur.Can(core.PermissionEditUserInfo); err != nil && cur.User.ID != request.UserID {
		return err
	}
	user, err := db.Users.Get(request.UserID)
	if db.IsKeyNotFoundErr(err) {
		return errs.StatusNotFound
	} else if err != nil {
		return err
	}
	if request.Email == user.PrimaryEmail {
		return errs.StatusError{http.StatusBadRequest, errors.New("cannot delete primary email")}
	}
	updatedEmailList, err := deleteEmail(user.Emails, request.Email)
	if err != nil {
		return err
	}
	user.Emails = updatedEmailList
	if err = db.Users.Set(user); err != nil {
		return err
	}

	return json.NewEncoder(w).Encode(user.Info())
}

func deleteEmail(emails []core.Email, email string) ([]core.Email, error) {
	var remainingVerifiedCount int
	var emailRecordForRemoval core.Email
	for i, record := range emails {
		if record.Email == email {
			emails = emails[:i+copy(emails[i:], emails[i+1:])]
			emailRecordForRemoval = record
		} else if record.Verified {
			// count number of verified emails
			remainingVerifiedCount++
		}
	}

	// you can always remove emails that are not verified
	if !emailRecordForRemoval.Verified {
		return emails, nil
	}
	// if this will eliminate the last verified email, you can't delete it
	if remainingVerifiedCount == 0 {
		return nil, errs.StatusError{http.StatusBadRequest, errors.New("cannot delete last verified email")}
	}

	return emails, nil
}

// POST /api/core/email?userID=123&email=blah@example.com&action=something
func emailActionHandler(
	db *database.Database,
	config configuration.Configuration,
	cur core.Current,
	vars map[string]string,
	w http.ResponseWriter,
	r *http.Request,
) error {
	var request ModifyEmailRecordRequest
	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		return err
	}
	action := vars["action"]
	// Must have permission to edit users unless editing oneself
	if err := cur.Can(core.PermissionEditUserInfo); err != nil && cur.User.ID != request.UserID {
		return err
	}
	user, err := db.Users.Get(request.UserID)
	if db.IsKeyNotFoundErr(err) {
		return errs.StatusNotFound
	} else if err != nil {
		return err
	}

	// process actions
	switch action {
	case "setPrimary":
		if !verifiedEmailExists(user.Emails, request.Email) {
			return errs.StatusError{http.StatusBadRequest, errors.New("primary email must be verified")}
		}
		user.PrimaryEmail = request.Email
		user.ModifiedDate = time.Now()
	case "add":
		exists, err := db.Users.EmailExists(request.Email)
		if err != nil {
			return err
		}
		if exists {
			return errs.StatusError{http.StatusBadRequest, errors.New("email already in use")}
		}
		// TODO: set verification code
		// TODO: send email, once implemented
		record := core.Email{
			Email:                request.Email,
			Verified:             false,
			VerifiedDate:         time.Time{},
			VerificationCode:     "",
			VerificationCodeDate: time.Time{},
		}
		user.Emails = append(user.Emails, record)
	case "resendVerificationMessage":
		// TODO: update code and send email, once implemented
	}
	if err = db.Users.Set(user); err != nil {
		return err
	}

	return json.NewEncoder(w).Encode(user.Info())
}

func verifiedEmailExists(emails []core.Email, email string) bool {
	for _, record := range emails {
		if record.Email == email && record.Verified {
			return true
		}
	}
	return false
}

type ModifyEmailRecordRequest struct {
	Email  string         `json:"email"`
	UserID store.Identity `json:"userID"`
}
<|endoftext|>