<commit_before>package sqlbuilder

import (
	"reflect"
	"testing"
)

func TestSelect(t *testing.T) {
	table1, _ := NewTable(
		"TABLE_A",
		IntColumn("id", false),
		IntColumn("test1", false),
		IntColumn("test2", false),
	)

	query, attrs, err := Select(table1.C("test1"), table1.C("test2")).
		From(table1).
		Where(
		And(
			Eq(table1.C("id"), L(1)),
			Eq(table1.C("test1"), L(2)),
		)).
		ToSql()
	if query != `SELECT "TABLE_A"."test1" "TABLE_A"."test2" FROM "TABLE_A" WHERE "TABLE_A"."id"=? AND "TABLE_A"."test1"=?;` {
		t.Error("got", query)
	}
	if !reflect.DeepEqual(attrs, []interface{}{int64(1), int64(2)}) {
		t.Error("got", attrs)
	}
	if err != nil {
		t.Error("got", err)
	}
}
<commit_msg>Added benchmark for SELECT statement<commit_after>package sqlbuilder

import (
	"reflect"
	"testing"
)

func TestSelect(t *testing.T) {
	table1, _ := NewTable(
		"TABLE_A",
		IntColumn("id", false),
		IntColumn("test1", false),
		IntColumn("test2", false),
	)

	query, attrs, err := Select(table1.C("test1"), table1.C("test2")).
		From(table1).
		Where(
		And(
			Eq(table1.C("id"), L(1)),
			Eq(table1.C("test1"), L(2)),
		)).
		ToSql()
	if query != `SELECT "TABLE_A"."test1" "TABLE_A"."test2" FROM "TABLE_A" WHERE "TABLE_A"."id"=? AND "TABLE_A"."test1"=?;` {
		t.Error("got", query)
	}
	if !reflect.DeepEqual(attrs, []interface{}{int64(1), int64(2)}) {
		t.Error("got", attrs)
	}
	if err != nil {
		t.Error("got", err)
	}
}

func BenchmarkSelect(b *testing.B) {
	table1, _ := NewTable(
		"TABLE_A",
		IntColumn("id", false),
		IntColumn("test1", false),
		IntColumn("test2", false),
	)

	for i := 0; i < b.N; i++ {
		Select(table1.C("test1"), table1.C("test2")).
			From(table1).
			Where(
			And(
				Eq(table1.C("id"), L(1)),
				Eq(table1.C("test1"), L(2)),
			)).
			ToSql()
	}
}
<|endoftext|>


<commit_before><commit_msg>nonEmptyEncoder only for numeric and string types<commit_after><|endoftext|>


<commit_before><commit_msg>added list functioanlity, dockerfile GET functionality, help functionality reads from external file, and is not marshalled in json response<commit_after><|endoftext|>


<commit_before><commit_msg>upload: add more debugging logs<commit_after><|endoftext|>


<commit_before>package mysql

import (
	"fmt";
)

type MySQLResultSet struct {
	FieldCount	uint64;
	Fields		[]*MySQLField;
	Rows		[]*MySQLRow;
}

type MySQLResponse struct {
	FieldCount	uint8;
	AffectedRows	uint64;
	InsertId	uint64;
	ServerStatus	uint16;
	WarningCount	uint16;
	Message		[]string;

	ResultSet	*MySQLResultSet;
}

func (r *MySQLResponse) String() string {
	var msg string;
	if r == nil {
		return "nil";
	}
	if r.FieldCount == 0x00 {
		msg = fmt.Sprintf("Response = OK")
	} else if r.FieldCount == 0xff {
		msg = fmt.Sprintf("Response = ERROR")
	} else {
		msg = fmt.Sprintf("Response = ResultSet")
	}
	if r.AffectedRows > 0 {
		msg = fmt.Sprintf("%s, Affected Rows = %d", msg, r.AffectedRows)
	}
	if r.InsertId > 0 {
		msg = fmt.Sprintf("%s, Insert Id = %d", msg, r.InsertId)
	}
	msg = fmt.Sprintf("%s, Server Status = %x", msg, r.ServerStatus);
	if r.WarningCount > 0 {
		msg = fmt.Sprintf("%s, Warnings = %x", msg, r.WarningCount)
	}
	if len(r.Message) > 0 {
		msg = fmt.Sprintf("%s, Message = %s", msg, r.Message)
	}
	return msg;
}

type MySQLField struct {
	Catalog		string;
	Db		string;
	Table		string;
	OrgTable	string;
	Name		string;
	OrgName		string;

	Charset		uint16;
	Length		uint32;
	Type		uint8;
	Flags		uint16;
	Decimals	uint8;
	Default		uint64;
}

func (f *MySQLField) String() string	{ return f.Name }

type MySQLData struct {
	Data	[]byte;
	Length	uint64;
	IsNull	bool;
	Type	uint8;
}

func (d *MySQLData) String() string {
	if d.IsNull {
		return "NULL"
	}
	return string(d.Data);
}

type MySQLRow struct {
	Data []*MySQLData;
}
<commit_msg>gofmt<commit_after>package mysql

import (
	"fmt";
)

type MySQLResultSet struct {
	FieldCount	uint64;
	Fields		[]*MySQLField;
	Rows		[]*MySQLRow;
}

type MySQLResponse struct {
	FieldCount	uint8;
	AffectedRows	uint64;
	InsertId	uint64;
	ServerStatus	uint16;
	WarningCount	uint16;
	Message		[]string;

	ResultSet	*MySQLResultSet;
}

func (r *MySQLResponse) String() string {
	var msg string;
	if r == nil {
		return "nil"
	}
	if r.FieldCount == 0x00 {
		msg = fmt.Sprintf("Response = OK")
	} else if r.FieldCount == 0xff {
		msg = fmt.Sprintf("Response = ERROR")
	} else {
		msg = fmt.Sprintf("Response = ResultSet")
	}
	if r.AffectedRows > 0 {
		msg = fmt.Sprintf("%s, Affected Rows = %d", msg, r.AffectedRows)
	}
	if r.InsertId > 0 {
		msg = fmt.Sprintf("%s, Insert Id = %d", msg, r.InsertId)
	}
	msg = fmt.Sprintf("%s, Server Status = %x", msg, r.ServerStatus);
	if r.WarningCount > 0 {
		msg = fmt.Sprintf("%s, Warnings = %x", msg, r.WarningCount)
	}
	if len(r.Message) > 0 {
		msg = fmt.Sprintf("%s, Message = %s", msg, r.Message)
	}
	return msg;
}

type MySQLField struct {
	Catalog		string;
	Db		string;
	Table		string;
	OrgTable	string;
	Name		string;
	OrgName		string;

	Charset		uint16;
	Length		uint32;
	Type		uint8;
	Flags		uint16;
	Decimals	uint8;
	Default		uint64;
}

func (f *MySQLField) String() string	{ return f.Name }

type MySQLData struct {
	Data	[]byte;
	Length	uint64;
	IsNull	bool;
	Type	uint8;
}

func (d *MySQLData) String() string {
	if d.IsNull {
		return "NULL"
	}
	return string(d.Data);
}

type MySQLRow struct {
	Data []*MySQLData;
}
<|endoftext|>


<commit_before>package splibaux

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	files      map[string]string
	ignorefile string
	verbosity  int
	nr         *strings.Replacer
)

func init() {
	wd, _ := os.Getwd()
	ignorefile = filepath.Join(wd, os.Getenv("SP_JOBNAME")+".pdf")
	files = make(map[string]string)
	if v := os.Getenv("SP_VERBOSITY"); v != "" {
		verbosity, _ = strconv.Atoi(v)
	}
	nr = strings.NewReplacer("\n", `\n`, `"`, `\"`, `\`, `\\`)
}

func downloadFile(resourceURL string, outfile io.Writer) error {
	// get HTTP file
	res, err := http.Get(resourceURL)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return fmt.Errorf("Resource not found")
	}
	_, err = io.Copy(outfile, res.Body)
	return err
}

// Download a file via http / https and save it into a file in the imagecache folder.
// If the environment variable CACHEMETHOD is set to 'optimal', the method docaching()
// will perform a query to the server even if the image file exists in the IMGCACHE directory
// to check if the local file is up to date.
// The return value is the file name for the LuaTeX process
func saveFileFromURL(parsedURL *url.URL, rawURL string) (string, error) {
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}

	if fi, err := os.Stat(rawimgcache); os.IsExist(err) {
		// file exists
		if !fi.IsDir() {
			fmt.Println("Image cache exists but is not a directory")
			return "", fmt.Errorf("Image cache %q exists but is not a directory", rawimgcache)
		}
	} else {
		err = os.MkdirAll(rawimgcache, 0755)
		if err != nil {
			return "", err
		}
	}

	destfile := parsedURL.Hostname() + parsedURL.Path
	if parsedURL.RawQuery != "" {
		destfile += "?" + parsedURL.RawQuery
	}

	// docaching does not do anything if the cache method is not "optimal".
	// So the resultingFilename may not exist. But at least we now know the
	// filename (it is basically a md5 sum of the URL, but this is not guaranteed).
	resultingFilename, err := getFilenameAndDoCaching(rawimgcache, destfile, rawURL)
	if err != nil {
		return "", err
	}
	if cachemethod != "none" {
		// docaching has downloaded the file, so we can pass it back
		// to the lua process
		if _, err = os.Stat(resultingFilename); err == nil {
			return resultingFilename, nil
		}
		// only keep on going if the error of stat is a "file not found" error.
		if !os.IsNotExist(err) {
			return "", err
		}
	}

	// We create a temporary file and use that for downloading.
	// After that (the process can take some time) we create the file we need.
	f, err := ioutil.TempFile(rawimgcache, "download")
	if err != nil {
		return "", err
	}
	defer f.Close()
	err = downloadFile(rawURL, f)
	if err != nil {
		return "", err
	}
	err = os.Rename(f.Name(), resultingFilename)
	return resultingFilename, err
}

// GetFullPath returns the real path of a file.
// filename can be a filename (w/o path), an absolute path to a file,
// a file:// or a http(s):// URL.
func GetFullPath(filename string) (string, error) {
	u, err := url.Parse(filename)
	if err != nil {
		return "", err
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return saveFileFromURL(u, filename)
	} else if u.Scheme == "file" {
		fn := u.Path
		if hn := u.Hostname(); hn != "" {
			fn = hn + ":" + fn
		}
		return LookupFile(fn), nil
	} else {
		return LookupFile(filename), nil
	}
}

// LookupFile returns the full path of the given file name. The file name can be a
// simple name, a path, a URL etc.
func LookupFile(path string) string {
	// TODO: lowercase
	// local lowercase = os.getenv("SP_IGNORECASE") == "1"
	//  if lowercase then filename_or_uri = unicode.utf8.lower(filename_or_uri) end
	if ret, ok := files[path]; ok {
		return ret
	}
	if _, err := os.Stat(path); err == nil {
		return path
	}
	return ""
}

func addFileToList(path string, info os.FileInfo, err error) error {
	if info != nil {
		if !info.IsDir() {
			if ignorefile != path {
				files[filepath.Base(path)] = path
			}
		}
	}
	return nil
}

// AddDir adds a directory to the list of search paths.
func AddDir(p string) {
	filepath.Walk(p, addFileToList)
}

// BuildFilelist walks through every given path and adds all found
// files to the global file search list.
func BuildFilelist(paths []string) {
	var err error
	for _, p := range paths {
		if !filepath.IsAbs(p) {
			p, err = filepath.Abs(p)
			if err != nil {
				fmt.Println(err)
				continue
			}
		}
		filepath.Walk(p, addFileToList)
	}

	if os.Getenv("SP_IGNORECASE") == "1" {
		newMap := make(map[string]string, len(files))
		for k, v := range files {
			newMap[strings.ToLower(k)] = v
		}
		files = newMap
	}
}

func isFont(filename string) bool {
	lc := strings.ToLower(filename)
	return strings.HasSuffix(lc, ".pfb") || strings.HasSuffix(lc, ".ttf") || strings.HasSuffix(lc, ".otf")
}

// ListFonts returns a string of all font files found in the paths.
func ListFonts() []string {
	var res []string
	for _, f := range files {
		if isFont(f) {
			res = append(res, f)
		}
	}
	return res

}

func writeContentsToTempfile(contents string) (string, error) {
	var filename string
	f, err := ioutil.TempFile("", "speedata")
	if err != nil {
		return "", err
	}

	stringReader := strings.NewReader(contents)
	_, err = io.Copy(f, stringReader)
	if err != nil {
		return "", err
	}
	filename = f.Name()
	f.Close()
	return filename, nil
}

func convertFile(inputfilename, baseoutputfilename, handler string) (string, error) {
	var err error
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}
	err = os.MkdirAll(rawimgcache, 0755)
	if err != nil {
		return "", err
	}

	outfile := filepath.Join(rawimgcache, baseoutputfilename)
	replacer := strings.NewReplacer("%%input%%", inputfilename, "%%output%%", outfile)
	replacedHandler := strings.Fields(replacer.Replace(handler))
	executableFile := replacedHandler[0]
	replacedHandler = replacedHandler[1:]
	for _, itm := range replacedHandler {
		if strings.HasPrefix(itm, outfile) {
			outfile = itm
		}
	}
	cmd := exec.Command(executableFile, replacedHandler...)
	if verbosity > 0 {
		fmt.Println("command for image conversion:", cmd)
	}
	err = cmd.Run()
	return outfile, err
}

// ConvertContents runs an external program to convert the image into a file
// format suitable for the speedata Publisher.
func ConvertContents(contents, handler string) (string, error) {
	inputfilename, err := writeContentsToTempfile(contents)
	if err != nil {
		return "", err
	}
	hashedFilename := fmt.Sprintf("%x", md5.Sum([]byte(contents)))

	if verbosity == 0 {
		defer func() {
			os.RemoveAll(inputfilename)
		}()
	}
	return convertFile(inputfilename, hashedFilename, handler)
}

// ConvertImage runs an external program to convert the image into a file
// format suitable for the speedata Publisher.
func ConvertImage(filename, handler string) (string, error) {
	return convertFile(filename, "outfilename", handler)
}

// ConvertSVGImage runs inkscape to convert an SVG image to PDF.
// It returns the PDF file and an error.
func ConvertSVGImage(filename string) (string, error) {
	svgfile, err := GetFullPath(filename)
	if err != nil {
		return "", err
	}
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}
	err = os.MkdirAll(rawimgcache, 0755)
	if err != nil {
		return "", err
	}

	hashedFilename := fmt.Sprintf("%x", md5.Sum([]byte(svgfile)))
	pdffile := filepath.Join(rawimgcache, hashedFilename+".pdf")

	if _, err := os.Stat(pdffile); err == nil {
		return pdffile, nil
	}

	binaryname := os.Getenv("SP_INKSCAPE")
	if binaryname == "" {
		fmt.Println("SP_INKSCAPE should be set. Why is it empty?")
		binaryname = "inkscape"
	}
	argument := os.Getenv("SP_INKSCAPECMD")

	fmt.Print("Running inkscape on ", svgfile, "...")
	cmd := exec.Command(binaryname, argument, pdffile, svgfile)
	out, err := cmd.CombinedOutput()
	fmt.Println("done. Output follows (if any):")
	fmt.Println(string(out))
	if err != nil {
		return "", err
	}
	return pdffile, nil
}

func handleXInclude(href string, startindex, indent int) (string, error) {
	fullpath := LookupFile(href)
	f, err := os.Open(fullpath)
	if err != nil {
		return "", err
	}
	return readXMLFile(f, startindex, indent)
}

func ReadXMLFile(filename string) (string, error) {
	fullpath := LookupFile(filename)
	f, err := os.Open(fullpath)
	if err != nil {
		return "", err
	}
	str, err := readXMLFile(f, 1, 0)
	return "tbl = {" + str + "}", err
}
<commit_msg>Re-implement path rewrite<commit_after>package splibaux

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
)

var (
	files       map[string]string = make(map[string]string)
	ignorefile  string
	verbosity   int
	pathrewrite *strings.Replacer
	nr          *strings.Replacer
)

func init() {
	wd, _ := os.Getwd()
	ignorefile = filepath.Join(wd, os.Getenv("SP_JOBNAME")+".pdf")
	if v := os.Getenv("SP_VERBOSITY"); v != "" {
		verbosity, _ = strconv.Atoi(v)
	}
	nr = strings.NewReplacer("\n", `\n`, `"`, `\"`, `\`, `\\`)

	if rewriteString := os.Getenv("SP_PATH_REWRITE"); rewriteString != "" {
		var rewrites = []string{}

		elements := strings.Split(rewriteString, ",")
		for _, elt := range elements {
			kv := strings.Split(elt, "=")
			if len(kv) == 2 {
				rewrites = append(rewrites, kv[0])
				rewrites = append(rewrites, kv[1])
			}
		}
		pathrewrite = strings.NewReplacer(rewrites...)
	}
}

func downloadFile(resourceURL string, outfile io.Writer) error {
	// get HTTP file
	res, err := http.Get(resourceURL)
	if err != nil {
		return err
	}
	defer res.Body.Close()
	if res.StatusCode != 200 {
		return fmt.Errorf("Resource not found")
	}
	_, err = io.Copy(outfile, res.Body)
	return err
}

// Download a file via http / https and save it into a file in the imagecache folder.
// If the environment variable CACHEMETHOD is set to 'optimal', the method docaching()
// will perform a query to the server even if the image file exists in the IMGCACHE directory
// to check if the local file is up to date.
// The return value is the file name for the LuaTeX process
func saveFileFromURL(parsedURL *url.URL, rawURL string) (string, error) {
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}

	if fi, err := os.Stat(rawimgcache); os.IsExist(err) {
		// file exists
		if !fi.IsDir() {
			fmt.Println("Image cache exists but is not a directory")
			return "", fmt.Errorf("Image cache %q exists but is not a directory", rawimgcache)
		}
	} else {
		err = os.MkdirAll(rawimgcache, 0755)
		if err != nil {
			return "", err
		}
	}

	destfile := parsedURL.Hostname() + parsedURL.Path
	if parsedURL.RawQuery != "" {
		destfile += "?" + parsedURL.RawQuery
	}

	// docaching does not do anything if the cache method is not "optimal".
	// So the resultingFilename may not exist. But at least we now know the
	// filename (it is basically a md5 sum of the URL, but this is not guaranteed).
	resultingFilename, err := getFilenameAndDoCaching(rawimgcache, destfile, rawURL)
	if err != nil {
		return "", err
	}
	if cachemethod != "none" {
		// docaching has downloaded the file, so we can pass it back
		// to the lua process
		if _, err = os.Stat(resultingFilename); err == nil {
			return resultingFilename, nil
		}
		// only keep on going if the error of stat is a "file not found" error.
		if !os.IsNotExist(err) {
			return "", err
		}
	}

	// We create a temporary file and use that for downloading.
	// After that (the process can take some time) we create the file we need.
	f, err := ioutil.TempFile(rawimgcache, "download")
	if err != nil {
		return "", err
	}
	defer f.Close()
	err = downloadFile(rawURL, f)
	if err != nil {
		return "", err
	}
	err = os.Rename(f.Name(), resultingFilename)
	return resultingFilename, err
}

// GetFullPath returns the real path of a file.
// filename can be a filename (w/o path), an absolute path to a file,
// a file:// or a http(s):// URL.
func GetFullPath(filename string) (string, error) {
	u, err := url.Parse(filename)
	if err != nil {
		return "", err
	}
	if u.Scheme == "http" || u.Scheme == "https" {
		return saveFileFromURL(u, filename)
	} else if u.Scheme == "file" {
		fn := u.Path
		if hn := u.Hostname(); hn != "" {
			fn = hn + ":" + fn
		}
		return LookupFile(fn), nil
	} else {
		return LookupFile(filename), nil
	}
}

// LookupFile returns the full path of the given file name. The file name can be a
// simple name, a path, a URL etc.
func LookupFile(path string) string {
	// TODO: lowercase
	// local lowercase = os.getenv("SP_IGNORECASE") == "1"
	//  if lowercase then filename_or_uri = unicode.utf8.lower(filename_or_uri) end
	if pathrewrite != nil {
		path = pathrewrite.Replace(path)
	}

	if ret, ok := files[path]; ok {
		return ret
	}
	if _, err := os.Stat(path); err == nil {
		return path
	}
	return ""
}

func addFileToList(path string, info os.FileInfo, err error) error {
	if info != nil {
		if !info.IsDir() {
			if ignorefile != path {
				files[filepath.Base(path)] = path
			}
		}
	}
	return nil
}

// AddDir adds a directory to the list of search paths.
func AddDir(p string) {
	filepath.Walk(p, addFileToList)
}

// BuildFilelist walks through every given path and adds all found
// files to the global file search list.
func BuildFilelist(paths []string) {
	var err error
	for _, p := range paths {
		if !filepath.IsAbs(p) {
			p, err = filepath.Abs(p)
			if err != nil {
				fmt.Println(err)
				continue
			}
		}
		filepath.Walk(p, addFileToList)
	}

	if os.Getenv("SP_IGNORECASE") == "1" {
		newMap := make(map[string]string, len(files))
		for k, v := range files {
			newMap[strings.ToLower(k)] = v
		}
		files = newMap
	}
}

func isFont(filename string) bool {
	lc := strings.ToLower(filename)
	return strings.HasSuffix(lc, ".pfb") || strings.HasSuffix(lc, ".ttf") || strings.HasSuffix(lc, ".otf")
}

// ListFonts returns a string of all font files found in the paths.
func ListFonts() []string {
	var res []string
	for _, f := range files {
		if isFont(f) {
			res = append(res, f)
		}
	}
	return res

}

func writeContentsToTempfile(contents string) (string, error) {
	var filename string
	f, err := ioutil.TempFile("", "speedata")
	if err != nil {
		return "", err
	}

	stringReader := strings.NewReader(contents)
	_, err = io.Copy(f, stringReader)
	if err != nil {
		return "", err
	}
	filename = f.Name()
	f.Close()
	return filename, nil
}

func convertFile(inputfilename, baseoutputfilename, handler string) (string, error) {
	var err error
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}
	err = os.MkdirAll(rawimgcache, 0755)
	if err != nil {
		return "", err
	}

	outfile := filepath.Join(rawimgcache, baseoutputfilename)
	replacer := strings.NewReplacer("%%input%%", inputfilename, "%%output%%", outfile)
	replacedHandler := strings.Fields(replacer.Replace(handler))
	executableFile := replacedHandler[0]
	replacedHandler = replacedHandler[1:]
	for _, itm := range replacedHandler {
		if strings.HasPrefix(itm, outfile) {
			outfile = itm
		}
	}
	cmd := exec.Command(executableFile, replacedHandler...)
	if verbosity > 0 {
		fmt.Println("command for image conversion:", cmd)
	}
	err = cmd.Run()
	return outfile, err
}

// ConvertContents runs an external program to convert the image into a file
// format suitable for the speedata Publisher.
func ConvertContents(contents, handler string) (string, error) {
	inputfilename, err := writeContentsToTempfile(contents)
	if err != nil {
		return "", err
	}
	hashedFilename := fmt.Sprintf("%x", md5.Sum([]byte(contents)))

	if verbosity == 0 {
		defer func() {
			os.RemoveAll(inputfilename)
		}()
	}
	return convertFile(inputfilename, hashedFilename, handler)
}

// ConvertImage runs an external program to convert the image into a file
// format suitable for the speedata Publisher.
func ConvertImage(filename, handler string) (string, error) {
	return convertFile(filename, "outfilename", handler)
}

// ConvertSVGImage runs inkscape to convert an SVG image to PDF.
// It returns the PDF file and an error.
func ConvertSVGImage(filename string) (string, error) {
	svgfile, err := GetFullPath(filename)
	if err != nil {
		return "", err
	}
	rawimgcache := os.Getenv("IMGCACHE")
	if rawimgcache == "" {
		rawimgcache = filepath.Join(os.TempDir(), "imagecache")
	}
	err = os.MkdirAll(rawimgcache, 0755)
	if err != nil {
		return "", err
	}

	hashedFilename := fmt.Sprintf("%x", md5.Sum([]byte(svgfile)))
	pdffile := filepath.Join(rawimgcache, hashedFilename+".pdf")

	if _, err := os.Stat(pdffile); err == nil {
		return pdffile, nil
	}

	binaryname := os.Getenv("SP_INKSCAPE")
	if binaryname == "" {
		fmt.Println("SP_INKSCAPE should be set. Why is it empty?")
		binaryname = "inkscape"
	}
	argument := os.Getenv("SP_INKSCAPECMD")

	fmt.Print("Running inkscape on ", svgfile, "...")
	cmd := exec.Command(binaryname, argument, pdffile, svgfile)
	out, err := cmd.CombinedOutput()
	fmt.Println("done. Output follows (if any):")
	fmt.Println(string(out))
	if err != nil {
		return "", err
	}
	return pdffile, nil
}

func handleXInclude(href string, startindex, indent int) (string, error) {
	fullpath := LookupFile(href)
	f, err := os.Open(fullpath)
	if err != nil {
		return "", err
	}
	return readXMLFile(f, startindex, indent)
}

func ReadXMLFile(filename string) (string, error) {
	fullpath := LookupFile(filename)
	f, err := os.Open(fullpath)
	if err != nil {
		return "", err
	}
	str, err := readXMLFile(f, 1, 0)
	return "tbl = {" + str + "}", err
}
<|endoftext|>


<commit_before>//The server package privdes the basic gofire server
package server

import (
	"encoding/json"
	"net/http"
)

type FireServer struct {
	Name                string
	Addr                string `json:"-"`
	RegisteredChatRooms []string
}

//a fireserver instance
var fireServer = new(FireServer)
var restCommands = make(map[string]string)

func init() {
	AddRestCommand("/api", MainHandler, "Get all commands")
	AddRestCommand("/api/c", ChatRoomHandler, "Get all chatrooms")

	//initServer()
}

//adds a rest command
func AddRestCommand(pattern string, handler func(http.ResponseWriter, *http.Request), desc string) {
	restCommands[pattern] = desc
	http.HandleFunc(pattern, handler)
}

func ListenAndServe(addr string) error {
	fireServer.Addr = addr
	err := http.ListenAndServe(addr, nil)
	return err
}

func initServer() {
	http.HandleFunc("/", MainHandler)
	http.HandleFunc(CHAT, ChatRoomHandler)
}

func MainHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path[len(r.URL.Path)-3:] == "api" {
		apiHandler(w, r)
	}
}

//Chat-rounting
const CHAT = "/api/c"

//ChatRoomHandler
func ChatRoomHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		chatName := r.URL.Path[len(CHAT):]
		if len(chatName) == 0 {
			getAllChatrooms(w, r)
		} else {
			w.Write([]byte(chatName))
		}
	}

	if r.Method == "POST" {
		err := r.ParseForm()
		if err != nil{
			//TODO Write error better
			w.Write([]byte("404"))
		}else{
			name := r.FormValue("name")
			fireServer.RegisteredChatRooms = append(fireServer.RegisteredChatRooms, name)
			w.Write([]byte(name))
		}
	}
}

// get /c/
func getAllChatrooms(w http.ResponseWriter, r *http.Request) {
	json, err := json.Marshal(fireServer.RegisteredChatRooms)
	if err == nil {
		w.Write(json)
	} else {
		w.Write([]byte("Fail"))
	}
}

func addChatRoom() {

}

//Api Hanlder, Handles /api calls on server
func apiHandler(w http.ResponseWriter, r *http.Request) {
	json, err := json.Marshal(restCommands)
	if err != nil {
		w.Write([]byte("Fail"))
	} else {
		w.Write(json)
	}
}
<commit_msg>some restructuring<commit_after>//The server package privdes the basic gofire server
package server

import (
	"encoding/json"
	"net/http"
)

//Rounting of the restful api
const(
	API = "/api"
	CHAT = "/api/c"
)

type FireServer struct {
	Name                string
	Addr                string `json:"-"`
	RegisteredChatRooms []string
}

//a fireserver instance
var fireServer = new(FireServer)
var restCommands = make(map[string]string)

func init() {
	AddRestCommand(API, ApiHandler, "Get all commands")
	AddRestCommand(CHAT, ChatRoomHandler, "Get all chatrooms")

	//initServer()
}

//adds a rest command
func AddRestCommand(pattern string, handler func(http.ResponseWriter, *http.Request), desc string) {
	restCommands[pattern] = desc
	http.HandleFunc(pattern, handler)
}

//A wrapper for the ListenAndServe of net/http
func ListenAndServe(addr string) error {
	fireServer.Addr = addr
	err := http.ListenAndServe(addr, nil)
	return err
}

func ApiHandler(w http.ResponseWriter, r *http.Request){
	json, err := json.Marshal(restCommands)
	if err != nil {
		w.Write([]byte("404"))
	} else {
		w.Write(json)
	}
}

//ChatRoomHandler
func ChatRoomHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		chatName := r.URL.Path[len(CHAT):]
		if len(chatName) == 0 {
			getAllChatrooms(w, r)
		} else {
			w.Write([]byte(chatName))
		}
	}

	if r.Method == "POST" {
		err := r.ParseForm()
		if err != nil{
			//TODO Write error better
			w.Write([]byte("404"))
		}else{
			name := r.FormValue("name")
			fireServer.RegisteredChatRooms = append(fireServer.RegisteredChatRooms, name)
			w.Write([]byte(name))
		}
	}
}

// get /c/
func getAllChatrooms(w http.ResponseWriter, r *http.Request) {
	json, err := json.Marshal(fireServer.RegisteredChatRooms)
	if err == nil {
		w.Write(json)
	} else {
		w.Write([]byte("Fail"))
	}
}
<|endoftext|>


<commit_before>package gs

import (
	"gosubli.me/mg"
)

var (
	statusChan  = mg.Chan("margo.status")
	errorDrawer = Drawer("mg.error")
	noteDrawer  = Drawer("mg.note")
)

type Drawer string

func (d Drawer) SetText(text string) {
	statusChan.Send(struct{ Key, Text string }{string(d), text})
}

func Error(text string) {
	errorDrawer.SetText(text)
}

func Note(text string) {
	noteDrawer.SetText(text)
}
<commit_msg>fix the status drawer names. using incorrect name causes new, normal drawers to be created so their text would linger in the status bar forever<commit_after>package gs

import (
	"gosubli.me/mg"
)

var (
	statusChan  = mg.Chan("margo.status")
	errorDrawer = Drawer("margo.error")
	noteDrawer  = Drawer("margo.note")
)

type Drawer string

func (d Drawer) SetText(text string) {
	statusChan.Send(struct{ Key, Text string }{string(d), text})
}

func Error(text string) {
	errorDrawer.SetText(text)
}

func Note(text string) {
	noteDrawer.SetText(text)
}
<|endoftext|>


<commit_before>package server

import (
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/revocation"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/sirupsen/logrus"
)

// Configuration contains configuration for the irmaserver library and irmad.
type Configuration struct {
	// irma_configuration. If not given, this will be popupated using SchemesPath.
	IrmaConfiguration *irma.Configuration `json:"-"`
	// Path to IRMA schemes to parse into IrmaConfiguration (only used if IrmaConfiguration == nil).
	// If left empty, default value is taken using DefaultSchemesPath().
	// If an empty folder is specified, default schemes (irma-demo and pbdf) are downloaded into it.
	SchemesPath string `json:"schemes_path" mapstructure:"schemes_path"`
	// If specified, schemes found here are copied into SchemesPath (only used if IrmaConfiguration == nil)
	SchemesAssetsPath string `json:"schemes_assets_path" mapstructure:"schemes_assets_path"`
	// Disable scheme updating
	DisableSchemesUpdate bool `json:"disable_schemes_update" mapstructure:"disable_schemes_update"`
	// Update all schemes every x minutes (default value 0 means 60) (use DisableSchemesUpdate to disable)
	SchemesUpdateInterval int `json:"schemes_update" mapstructure:"schemes_update"`
	// Path to issuer private keys to parse
	IssuerPrivateKeysPath string `json:"privkeys" mapstructure:"privkeys"`
	// URL at which the IRMA app can reach this server during sessions
	URL string `json:"url" mapstructure:"url"`
	// Required to be set to true if URL does not begin with https:// in production mode.
	// In this case, the server would communicate with IRMA apps over plain HTTP. You must otherwise
	// ensure (using eg a reverse proxy with TLS enabled) that the attributes are protected in transit.
	DisableTLS bool `json:"disable_tls" mapstructure:"disable_tls"`
	// (Optional) email address of server admin, for incidental notifications such as breaking API changes
	// See https://github.com/privacybydesign/irmago/tree/master/server#specifying-an-email-address
	// for more information
	Email string `json:"email" mapstructure:"email"`
	// Enable server sent events for status updates (experimental; tends to hang when a reverse proxy is used)
	EnableSSE bool `json:"enable_sse" mapstructure:"enable_sse"`

	// Static session requests that can be created by POST /session/{name}
	StaticSessions map[string]interface{} `json:"static_sessions"`
	// Static session requests after parsing
	StaticSessionRequests map[string]irma.RequestorRequest `json:"-"`

	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwt_issuer" mapstructure:"jwt_issuer"`
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey     string `json:"jwt_privkey" mapstructure:"jwt_privkey"`
	JwtPrivateKeyFile string `json:"jwt_privkey_file" mapstructure:"jwt_privkey_file"`
	// Parsed JWT private key
	JwtRSAPrivateKey *rsa.PrivateKey `json:"-"`

	// Logging verbosity level: 0 is normal, 1 includes DEBUG level, 2 includes TRACE level
	Verbose int `json:"verbose" mapstructure:"verbose"`
	// Don't log anything at all
	Quiet bool `json:"quiet" mapstructure:"quiet"`
	// Output structured log in JSON format
	LogJSON bool `json:"log_json" mapstructure:"log_json"`
	// Custom logger instance. If specified, Verbose, Quiet and LogJSON are ignored.
	Logger *logrus.Logger `json:"-"`

	// Connection string for revocation database
	RevocationDBConnStr string `json:"revocation_db_str" mapstructure:"revocation_db_str"`
	// Database type for revocation database, supported: postgres, mysql
	RevocationDBType string `json:"revocation_db_type" mapstructure:"revocation_db_type"`
	// Credentials types for which revocation database should be hosted
	RevocationSettings irma.RevocationSettings `json:"revocation_settings" mapstructure:"revocation_settings"`

	// Production mode: enables safer and stricter defaults and config checking
	Production bool `json:"production" mapstructure:"production"`
}

// Check ensures that the Configuration is loaded, usable and free of errors.
func (conf *Configuration) Check() error {
	if conf.Logger == nil {
		conf.Logger = NewLogger(conf.Verbose, conf.Quiet, conf.LogJSON)
	}
	Logger = conf.Logger
	irma.SetLogger(conf.Logger)

	// loop to avoid repetetive err != nil line triplets
	for _, f := range []func() error{
		conf.verifyIrmaConf,
		conf.verifyPrivateKeys,
		conf.verifyURL,
		conf.verifyEmail,
		conf.verifyRevocation,
		conf.verifyStaticSessions,
		conf.verifyJwtPrivateKey,
	} {
		if err := f(); err != nil {
			_ = LogError(err)
			if conf.IrmaConfiguration != nil && conf.IrmaConfiguration.Revocation != nil {
				if e := conf.IrmaConfiguration.Revocation.Close(); e != nil {
					_ = LogError(e)
				}
			}
			return err
		}
	}

	return nil
}

func (conf *Configuration) HavePrivateKeys() bool {
	var err error
	for id := range conf.IrmaConfiguration.Issuers {
		if _, err = conf.IrmaConfiguration.PrivateKeys.Latest(id); err == nil {
			return true
		}
	}
	return false
}

// helpers

func (conf *Configuration) verifyStaticSessions() error {
	conf.StaticSessionRequests = make(map[string]irma.RequestorRequest)
	for name, r := range conf.StaticSessions {
		if !regexp.MustCompile("^[a-zA-Z0-9_]+$").MatchString(name) {
			return errors.Errorf("static session name %s not allowed, must be alphanumeric", name)
		}
		j, err := json.Marshal(r)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		rrequest, err := ParseSessionRequest(j)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		action := rrequest.SessionRequest().Action()
		if action != irma.ActionDisclosing && action != irma.ActionSigning {
			return errors.Errorf("static session %s must be either a disclosing or signing session", name)
		}
		if rrequest.Base().CallbackURL == "" {
			return errors.Errorf("static session %s has no callback URL", name)
		}
		conf.StaticSessionRequests[name] = rrequest
	}
	return nil
}

func (conf *Configuration) verifyIrmaConf() error {
	if conf.IrmaConfiguration == nil {
		var (
			err    error
			exists bool
		)
		if conf.SchemesPath == "" {
			conf.SchemesPath = irma.DefaultSchemesPath() // Returns an existing path
		}
		if exists, err = common.PathExists(conf.SchemesPath); err != nil {
			return err
		}
		if !exists {
			return errors.Errorf("Nonexisting schemes_path provided: %s", conf.SchemesPath)
		}
		conf.Logger.WithField("schemes_path", conf.SchemesPath).Info("Determined schemes path")
		conf.IrmaConfiguration, err = irma.NewConfiguration(conf.SchemesPath, irma.ConfigurationOptions{
			Assets:              conf.SchemesAssetsPath,
			RevocationDBType:    conf.RevocationDBType,
			RevocationDBConnStr: conf.RevocationDBConnStr,
			RevocationSettings:  conf.RevocationSettings,
		})
		if err != nil {
			return err
		}
		if err = conf.IrmaConfiguration.ParseFolder(); err != nil {
			return err
		}
	}

	if len(conf.IrmaConfiguration.SchemeManagers) == 0 {
		conf.Logger.Infof("No schemes found in %s, downloading default (irma-demo and pbdf)", conf.SchemesPath)
		if err := conf.IrmaConfiguration.DownloadDefaultSchemes(); err != nil {
			return err
		}
	}
	if conf.SchemesUpdateInterval == 0 {
		conf.SchemesUpdateInterval = 60
	}
	if !conf.DisableSchemesUpdate {
		conf.IrmaConfiguration.AutoUpdateSchemes(uint(conf.SchemesUpdateInterval))
	}

	return nil
}

func (conf *Configuration) verifyPrivateKeys() error {
	if conf.IssuerPrivateKeysPath == "" {
		return nil
	}
	ring, err := irma.NewPrivateKeyRingFolder(conf.IssuerPrivateKeysPath, conf.IrmaConfiguration)
	if err != nil {
		return err
	}
	return conf.IrmaConfiguration.AddPrivateKeyRing(ring)
}

func (conf *Configuration) prepareRevocation(credid irma.CredentialTypeIdentifier) error {
	var sk *revocation.PrivateKey
	err := conf.IrmaConfiguration.PrivateKeys.Iterate(credid.IssuerIdentifier(), func(isk *gabi.PrivateKey) error {
		if !isk.RevocationSupported() {
			return nil
		}
		s, err := isk.RevocationKey()
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to load revocation private key %s-%d", credid, isk.Counter), 0)
		}
		sk = s
		exists, err := conf.IrmaConfiguration.Revocation.Exists(credid, isk.Counter)
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to check if accumulator exists for %s-%d", credid, isk.Counter), 0)
		}
		if !exists {
			conf.Logger.Warnf("Creating initial accumulator for %s-%d", credid, isk.Counter)
			if err := conf.IrmaConfiguration.Revocation.EnableRevocation(credid, sk); err != nil {
				return errors.WrapPrefix(err, fmt.Sprintf("failed create initial accumulator for %s-%d", credid, isk.Counter), 0)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.Errorf("revocation server mode enabled for %s but no private key installed", credid)
	}
	return nil
}

func (conf *Configuration) verifyRevocation() error {
	rev := conf.IrmaConfiguration.Revocation

	// viper lowercases configuration keys, so we have to un-lowercase them back.
	for id := range conf.IrmaConfiguration.CredentialTypes {
		lc := irma.NewCredentialTypeIdentifier(strings.ToLower(id.String()))
		if lc == id {
			continue
		}
		if s, ok := conf.RevocationSettings[lc]; ok {
			delete(conf.RevocationSettings, lc)
			conf.RevocationSettings[id] = s
		}
	}

	for credid, settings := range conf.RevocationSettings {
		if _, known := conf.IrmaConfiguration.CredentialTypes[credid]; !known {
			return errors.Errorf("unknown credential type %s in revocation settings", credid)
		}
		if settings.Authority {
			conf.Logger.Info("authoritative revocation server mode enabled for " + credid.String())
			if err := conf.prepareRevocation(credid); err != nil {
				return err
			}
		} else if settings.Server {
			conf.Logger.Info("revocation server mode enabled for " + credid.String())
		}
		if settings.Server {
			conf.Logger.Info("Being the revocation server for a credential type comes with special responsibilities. Failure can lead to all IRMA apps being unable to disclose credentials of this type. Read more at https://irma.app/docs/revocation/#issuer-responsibilities.")
		}
	}

	for credid, credtype := range conf.IrmaConfiguration.CredentialTypes {
		if !credtype.RevocationSupported() {
			continue
		}
		_, err := rev.Keys.PrivateKeyLatest(credid.IssuerIdentifier())
		haveSK := err == nil
		settings := conf.RevocationSettings[credid]
		if haveSK && (settings == nil || (settings.RevocationServerURL == "" && !settings.Server)) {
			message := "Revocation-supporting private key installed for %s, but no revocation server is configured: issuance sessions will always fail"
			if conf.IrmaConfiguration.SchemeManagers[credid.IssuerIdentifier().SchemeManagerIdentifier()].Demo {
				conf.Logger.Warnf(message, credid)
			} else {
				return errors.Errorf(message, credid)
			}
		}
		if settings != nil && settings.RevocationServerURL != "" {
			url := settings.RevocationServerURL
			if url[len(url)-1] == '/' {
				settings.RevocationServerURL = url[:len(url)-1]
			}
		}
	}

	return nil
}

func (conf *Configuration) verifyURL() error {
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
		}
		if !strings.HasPrefix(conf.URL, "https://") {
			if !conf.Production || conf.DisableTLS {
				conf.DisableTLS = true
				conf.Logger.Warnf("TLS is not enabled on the url \"%s\" to which the IRMA app will connect. "+
					"Ensure that attributes are encrypted in transit by either enabling TLS or adding TLS in a reverse proxy.", conf.URL)
			} else {
				return errors.Errorf("Running without TLS in production mode is unsafe without a reverse proxy. " +
					"Either use a https:// URL or explicitly disable TLS.")
			}
		}
	} else {
		conf.Logger.Warn("No url parameter specified in configuration; unless an url is elsewhere prepended in the QR, the IRMA client will not be able to connect")
	}
	return nil
}

func (conf *Configuration) verifyEmail() error {
	if conf.Email != "" {
		// Very basic sanity checks
		if !strings.Contains(conf.Email, "@") || strings.Contains(conf.Email, "\n") {
			return errors.New("Invalid email address specified")
		}
		t := irma.NewHTTPTransport("https://metrics.privacybydesign.foundation/history", true)
		t.SetHeader("User-Agent", "irmaserver")
		var x string
		_ = t.Post("email", &x, conf.Email)
	}
	return nil
}

func (conf *Configuration) verifyJwtPrivateKey() error {
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
		return nil
	}

	keybytes, err := common.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
	}

	conf.JwtRSAPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
	conf.Logger.Info("Private key parsed, JWT endpoints enabled")
	return err
}
<commit_msg>feat: irma server no longer emits warning on startup about private keys for demo schemes (i.e. irma-demo)<commit_after>package server

import (
	"crypto/rsa"
	"encoding/json"
	"fmt"
	"regexp"
	"strings"

	"github.com/dgrijalva/jwt-go"
	"github.com/go-errors/errors"
	"github.com/privacybydesign/gabi"
	"github.com/privacybydesign/gabi/revocation"
	irma "github.com/privacybydesign/irmago"
	"github.com/privacybydesign/irmago/internal/common"
	"github.com/sirupsen/logrus"
)

// Configuration contains configuration for the irmaserver library and irmad.
type Configuration struct {
	// irma_configuration. If not given, this will be popupated using SchemesPath.
	IrmaConfiguration *irma.Configuration `json:"-"`
	// Path to IRMA schemes to parse into IrmaConfiguration (only used if IrmaConfiguration == nil).
	// If left empty, default value is taken using DefaultSchemesPath().
	// If an empty folder is specified, default schemes (irma-demo and pbdf) are downloaded into it.
	SchemesPath string `json:"schemes_path" mapstructure:"schemes_path"`
	// If specified, schemes found here are copied into SchemesPath (only used if IrmaConfiguration == nil)
	SchemesAssetsPath string `json:"schemes_assets_path" mapstructure:"schemes_assets_path"`
	// Disable scheme updating
	DisableSchemesUpdate bool `json:"disable_schemes_update" mapstructure:"disable_schemes_update"`
	// Update all schemes every x minutes (default value 0 means 60) (use DisableSchemesUpdate to disable)
	SchemesUpdateInterval int `json:"schemes_update" mapstructure:"schemes_update"`
	// Path to issuer private keys to parse
	IssuerPrivateKeysPath string `json:"privkeys" mapstructure:"privkeys"`
	// URL at which the IRMA app can reach this server during sessions
	URL string `json:"url" mapstructure:"url"`
	// Required to be set to true if URL does not begin with https:// in production mode.
	// In this case, the server would communicate with IRMA apps over plain HTTP. You must otherwise
	// ensure (using eg a reverse proxy with TLS enabled) that the attributes are protected in transit.
	DisableTLS bool `json:"disable_tls" mapstructure:"disable_tls"`
	// (Optional) email address of server admin, for incidental notifications such as breaking API changes
	// See https://github.com/privacybydesign/irmago/tree/master/server#specifying-an-email-address
	// for more information
	Email string `json:"email" mapstructure:"email"`
	// Enable server sent events for status updates (experimental; tends to hang when a reverse proxy is used)
	EnableSSE bool `json:"enable_sse" mapstructure:"enable_sse"`

	// Static session requests that can be created by POST /session/{name}
	StaticSessions map[string]interface{} `json:"static_sessions"`
	// Static session requests after parsing
	StaticSessionRequests map[string]irma.RequestorRequest `json:"-"`

	// Used in the "iss" field of result JWTs from /result-jwt and /getproof
	JwtIssuer string `json:"jwt_issuer" mapstructure:"jwt_issuer"`
	// Private key to sign result JWTs with. If absent, /result-jwt and /getproof are disabled.
	JwtPrivateKey     string `json:"jwt_privkey" mapstructure:"jwt_privkey"`
	JwtPrivateKeyFile string `json:"jwt_privkey_file" mapstructure:"jwt_privkey_file"`
	// Parsed JWT private key
	JwtRSAPrivateKey *rsa.PrivateKey `json:"-"`

	// Logging verbosity level: 0 is normal, 1 includes DEBUG level, 2 includes TRACE level
	Verbose int `json:"verbose" mapstructure:"verbose"`
	// Don't log anything at all
	Quiet bool `json:"quiet" mapstructure:"quiet"`
	// Output structured log in JSON format
	LogJSON bool `json:"log_json" mapstructure:"log_json"`
	// Custom logger instance. If specified, Verbose, Quiet and LogJSON are ignored.
	Logger *logrus.Logger `json:"-"`

	// Connection string for revocation database
	RevocationDBConnStr string `json:"revocation_db_str" mapstructure:"revocation_db_str"`
	// Database type for revocation database, supported: postgres, mysql
	RevocationDBType string `json:"revocation_db_type" mapstructure:"revocation_db_type"`
	// Credentials types for which revocation database should be hosted
	RevocationSettings irma.RevocationSettings `json:"revocation_settings" mapstructure:"revocation_settings"`

	// Production mode: enables safer and stricter defaults and config checking
	Production bool `json:"production" mapstructure:"production"`
}

// Check ensures that the Configuration is loaded, usable and free of errors.
func (conf *Configuration) Check() error {
	if conf.Logger == nil {
		conf.Logger = NewLogger(conf.Verbose, conf.Quiet, conf.LogJSON)
	}
	Logger = conf.Logger
	irma.SetLogger(conf.Logger)

	// loop to avoid repetetive err != nil line triplets
	for _, f := range []func() error{
		conf.verifyIrmaConf,
		conf.verifyPrivateKeys,
		conf.verifyURL,
		conf.verifyEmail,
		conf.verifyRevocation,
		conf.verifyStaticSessions,
		conf.verifyJwtPrivateKey,
	} {
		if err := f(); err != nil {
			_ = LogError(err)
			if conf.IrmaConfiguration != nil && conf.IrmaConfiguration.Revocation != nil {
				if e := conf.IrmaConfiguration.Revocation.Close(); e != nil {
					_ = LogError(e)
				}
			}
			return err
		}
	}

	return nil
}

func (conf *Configuration) HavePrivateKeys() bool {
	var err error
	for id := range conf.IrmaConfiguration.Issuers {
		if conf.IrmaConfiguration.SchemeManagers[id.SchemeManagerIdentifier()].Demo {
			continue
		}
		if _, err = conf.IrmaConfiguration.PrivateKeys.Latest(id); err == nil {
			return true
		}
	}
	return false
}

// helpers

func (conf *Configuration) verifyStaticSessions() error {
	conf.StaticSessionRequests = make(map[string]irma.RequestorRequest)
	for name, r := range conf.StaticSessions {
		if !regexp.MustCompile("^[a-zA-Z0-9_]+$").MatchString(name) {
			return errors.Errorf("static session name %s not allowed, must be alphanumeric", name)
		}
		j, err := json.Marshal(r)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		rrequest, err := ParseSessionRequest(j)
		if err != nil {
			return errors.WrapPrefix(err, "failed to parse static session request "+name, 0)
		}
		action := rrequest.SessionRequest().Action()
		if action != irma.ActionDisclosing && action != irma.ActionSigning {
			return errors.Errorf("static session %s must be either a disclosing or signing session", name)
		}
		if rrequest.Base().CallbackURL == "" {
			return errors.Errorf("static session %s has no callback URL", name)
		}
		conf.StaticSessionRequests[name] = rrequest
	}
	return nil
}

func (conf *Configuration) verifyIrmaConf() error {
	if conf.IrmaConfiguration == nil {
		var (
			err    error
			exists bool
		)
		if conf.SchemesPath == "" {
			conf.SchemesPath = irma.DefaultSchemesPath() // Returns an existing path
		}
		if exists, err = common.PathExists(conf.SchemesPath); err != nil {
			return err
		}
		if !exists {
			return errors.Errorf("Nonexisting schemes_path provided: %s", conf.SchemesPath)
		}
		conf.Logger.WithField("schemes_path", conf.SchemesPath).Info("Determined schemes path")
		conf.IrmaConfiguration, err = irma.NewConfiguration(conf.SchemesPath, irma.ConfigurationOptions{
			Assets:              conf.SchemesAssetsPath,
			RevocationDBType:    conf.RevocationDBType,
			RevocationDBConnStr: conf.RevocationDBConnStr,
			RevocationSettings:  conf.RevocationSettings,
		})
		if err != nil {
			return err
		}
		if err = conf.IrmaConfiguration.ParseFolder(); err != nil {
			return err
		}
	}

	if len(conf.IrmaConfiguration.SchemeManagers) == 0 {
		conf.Logger.Infof("No schemes found in %s, downloading default (irma-demo and pbdf)", conf.SchemesPath)
		if err := conf.IrmaConfiguration.DownloadDefaultSchemes(); err != nil {
			return err
		}
	}
	if conf.SchemesUpdateInterval == 0 {
		conf.SchemesUpdateInterval = 60
	}
	if !conf.DisableSchemesUpdate {
		conf.IrmaConfiguration.AutoUpdateSchemes(uint(conf.SchemesUpdateInterval))
	}

	return nil
}

func (conf *Configuration) verifyPrivateKeys() error {
	if conf.IssuerPrivateKeysPath == "" {
		return nil
	}
	ring, err := irma.NewPrivateKeyRingFolder(conf.IssuerPrivateKeysPath, conf.IrmaConfiguration)
	if err != nil {
		return err
	}
	return conf.IrmaConfiguration.AddPrivateKeyRing(ring)
}

func (conf *Configuration) prepareRevocation(credid irma.CredentialTypeIdentifier) error {
	var sk *revocation.PrivateKey
	err := conf.IrmaConfiguration.PrivateKeys.Iterate(credid.IssuerIdentifier(), func(isk *gabi.PrivateKey) error {
		if !isk.RevocationSupported() {
			return nil
		}
		s, err := isk.RevocationKey()
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to load revocation private key %s-%d", credid, isk.Counter), 0)
		}
		sk = s
		exists, err := conf.IrmaConfiguration.Revocation.Exists(credid, isk.Counter)
		if err != nil {
			return errors.WrapPrefix(err, fmt.Sprintf("failed to check if accumulator exists for %s-%d", credid, isk.Counter), 0)
		}
		if !exists {
			conf.Logger.Warnf("Creating initial accumulator for %s-%d", credid, isk.Counter)
			if err := conf.IrmaConfiguration.Revocation.EnableRevocation(credid, sk); err != nil {
				return errors.WrapPrefix(err, fmt.Sprintf("failed create initial accumulator for %s-%d", credid, isk.Counter), 0)
			}
		}
		return nil
	})
	if err != nil {
		return err
	}
	if sk == nil {
		return errors.Errorf("revocation server mode enabled for %s but no private key installed", credid)
	}
	return nil
}

func (conf *Configuration) verifyRevocation() error {
	rev := conf.IrmaConfiguration.Revocation

	// viper lowercases configuration keys, so we have to un-lowercase them back.
	for id := range conf.IrmaConfiguration.CredentialTypes {
		lc := irma.NewCredentialTypeIdentifier(strings.ToLower(id.String()))
		if lc == id {
			continue
		}
		if s, ok := conf.RevocationSettings[lc]; ok {
			delete(conf.RevocationSettings, lc)
			conf.RevocationSettings[id] = s
		}
	}

	for credid, settings := range conf.RevocationSettings {
		if _, known := conf.IrmaConfiguration.CredentialTypes[credid]; !known {
			return errors.Errorf("unknown credential type %s in revocation settings", credid)
		}
		if settings.Authority {
			conf.Logger.Info("authoritative revocation server mode enabled for " + credid.String())
			if err := conf.prepareRevocation(credid); err != nil {
				return err
			}
		} else if settings.Server {
			conf.Logger.Info("revocation server mode enabled for " + credid.String())
		}
		if settings.Server {
			conf.Logger.Info("Being the revocation server for a credential type comes with special responsibilities. Failure can lead to all IRMA apps being unable to disclose credentials of this type. Read more at https://irma.app/docs/revocation/#issuer-responsibilities.")
		}
	}

	for credid, credtype := range conf.IrmaConfiguration.CredentialTypes {
		if !credtype.RevocationSupported() {
			continue
		}
		_, err := rev.Keys.PrivateKeyLatest(credid.IssuerIdentifier())
		haveSK := err == nil
		settings := conf.RevocationSettings[credid]
		if haveSK && (settings == nil || (settings.RevocationServerURL == "" && !settings.Server)) {
			message := "Revocation-supporting private key installed for %s, but no revocation server is configured: issuance sessions will always fail"
			if conf.IrmaConfiguration.SchemeManagers[credid.IssuerIdentifier().SchemeManagerIdentifier()].Demo {
				conf.Logger.Warnf(message, credid)
			} else {
				return errors.Errorf(message, credid)
			}
		}
		if settings != nil && settings.RevocationServerURL != "" {
			url := settings.RevocationServerURL
			if url[len(url)-1] == '/' {
				settings.RevocationServerURL = url[:len(url)-1]
			}
		}
	}

	return nil
}

func (conf *Configuration) verifyURL() error {
	if conf.URL != "" {
		if !strings.HasSuffix(conf.URL, "/") {
			conf.URL = conf.URL + "/"
		}
		if !strings.HasPrefix(conf.URL, "https://") {
			if !conf.Production || conf.DisableTLS {
				conf.DisableTLS = true
				conf.Logger.Warnf("TLS is not enabled on the url \"%s\" to which the IRMA app will connect. "+
					"Ensure that attributes are encrypted in transit by either enabling TLS or adding TLS in a reverse proxy.", conf.URL)
			} else {
				return errors.Errorf("Running without TLS in production mode is unsafe without a reverse proxy. " +
					"Either use a https:// URL or explicitly disable TLS.")
			}
		}
	} else {
		conf.Logger.Warn("No url parameter specified in configuration; unless an url is elsewhere prepended in the QR, the IRMA client will not be able to connect")
	}
	return nil
}

func (conf *Configuration) verifyEmail() error {
	if conf.Email != "" {
		// Very basic sanity checks
		if !strings.Contains(conf.Email, "@") || strings.Contains(conf.Email, "\n") {
			return errors.New("Invalid email address specified")
		}
		t := irma.NewHTTPTransport("https://metrics.privacybydesign.foundation/history", true)
		t.SetHeader("User-Agent", "irmaserver")
		var x string
		_ = t.Post("email", &x, conf.Email)
	}
	return nil
}

func (conf *Configuration) verifyJwtPrivateKey() error {
	if conf.JwtPrivateKey == "" && conf.JwtPrivateKeyFile == "" {
		return nil
	}

	keybytes, err := common.ReadKey(conf.JwtPrivateKey, conf.JwtPrivateKeyFile)
	if err != nil {
		return errors.WrapPrefix(err, "failed to read private key", 0)
	}

	conf.JwtRSAPrivateKey, err = jwt.ParseRSAPrivateKeyFromPEM(keybytes)
	conf.Logger.Info("Private key parsed, JWT endpoints enabled")
	return err
}
<|endoftext|>


<commit_before><commit_msg>Removed mode cli arg<commit_after><|endoftext|>


<commit_before>package opc

import (
	"fmt"
	"testing"

	"github.com/hashicorp/go-oracle-terraform/compute"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccOPCSecurityAssociation_Basic(t *testing.T) {
	ri := acctest.RandInt()
	config := fmt.Sprintf(testAccSecurityAssociationBasic, ri, ri)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccOPCCheckSecurityAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccOPCCheckSecurityAssociationExists,
				),
			},
		},
	})
}

func TestAccOPCSecurityAssociation_Complete(t *testing.T) {
	ri := acctest.RandInt()
	config := fmt.Sprintf(testAccSecurityAssociationComplete, ri, ri, ri)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccOPCCheckSecurityAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccOPCCheckSecurityAssociationExists,
				),
			},
		},
	})
}

func testAccOPCCheckSecurityAssociationExists(s *terraform.State) error {
	client := testAccProvider.Meta().(*compute.Client).SecurityAssociations()

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "opc_compute_security_association" {
			continue
		}

		input := compute.GetSecurityAssociationInput{
			Name: rs.Primary.Attributes["name"],
		}
		if _, err := client.GetSecurityAssociation(&input); err != nil {
			return fmt.Errorf("Error retrieving state of Security Association %s: %s", input.Name, err)
		}
	}

	return nil
}

func testAccOPCCheckSecurityAssociationDestroy(s *terraform.State) error {
	client := testAccProvider.Meta().(*compute.Client).SecurityAssociations()

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "opc_compute_security_association" {
			continue
		}

		input := compute.GetSecurityAssociationInput{
			Name: rs.Primary.Attributes["name"],
		}
		if info, err := client.GetSecurityAssociation(&input); err == nil {
			return fmt.Errorf("Security Association %s still exists: %#v", input.Name, info)
		}
	}

	return nil
}

var testAccSecurityAssociationBasic = `
resource "opc_compute_security_list" "test" {
  name                 = "acc-test-sec-ass-sec-list-%d"
  policy               = "PERMIT"
  outbound_cidr_policy = "DENY"
}

resource "opc_compute_instance" "test" {
  name        = "acc-test-sec-ass-instance-%d"
  label       = "Security Associations Test Instance"
  shape       = "oc3"
  image_list   = "/oracle/public/oel_6.7_apaas_16.4.5_1610211300"
}

resource "opc_compute_security_association" "test" {
  vcable  = "${opc_compute_instance.test.vcable_id}"
  seclist = "${opc_compute_security_list.test.name}"
}
`

var testAccSecurityAssociationComplete = `
resource "opc_compute_security_list" "test" {
  name                 = "acc-test-sec-ass-sec-list-%d"
  policy               = "PERMIT"
  outbound_cidr_policy = "DENY"
}

resource "opc_compute_instance" "test" {
  name        = "acc-test-sec-ass-instance-%d"
  label       = "Security Associations Test Instance"
  shape       = "oc3"
  image_list   = "/oracle/public/oel_6.7_apaas_16.4.5_1610211300"
}

resource "opc_compute_security_association" "test" {
  name    = "acc-test-sec-ass-%d"
  vcable  = "${opc_compute_instance.test.vcable}"
  seclist = "${opc_compute_security_list.test.name}"
}
`
<commit_msg>fixup tests<commit_after>package opc

import (
	"fmt"
	"testing"

	"github.com/hashicorp/go-oracle-terraform/compute"
	"github.com/hashicorp/terraform/helper/acctest"
	"github.com/hashicorp/terraform/helper/resource"
	"github.com/hashicorp/terraform/terraform"
)

func TestAccOPCSecurityAssociation_Basic(t *testing.T) {
	ri := acctest.RandInt()
	config := fmt.Sprintf(testAccSecurityAssociationBasic, ri, ri)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccOPCCheckSecurityAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccOPCCheckSecurityAssociationExists,
				),
			},
		},
	})
}

func TestAccOPCSecurityAssociation_Complete(t *testing.T) {
	ri := acctest.RandInt()
	config := fmt.Sprintf(testAccSecurityAssociationComplete, ri, ri, ri)

	resource.Test(t, resource.TestCase{
		PreCheck:     func() { testAccPreCheck(t) },
		Providers:    testAccProviders,
		CheckDestroy: testAccOPCCheckSecurityAssociationDestroy,
		Steps: []resource.TestStep{
			{
				Config: config,
				Check: resource.ComposeTestCheckFunc(
					testAccOPCCheckSecurityAssociationExists,
				),
			},
		},
	})
}

func testAccOPCCheckSecurityAssociationExists(s *terraform.State) error {
	client := testAccProvider.Meta().(*compute.Client).SecurityAssociations()

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "opc_compute_security_association" {
			continue
		}

		input := compute.GetSecurityAssociationInput{
			Name: rs.Primary.Attributes["name"],
		}
		if _, err := client.GetSecurityAssociation(&input); err != nil {
			return fmt.Errorf("Error retrieving state of Security Association %s: %s", input.Name, err)
		}
	}

	return nil
}

func testAccOPCCheckSecurityAssociationDestroy(s *terraform.State) error {
	client := testAccProvider.Meta().(*compute.Client).SecurityAssociations()

	for _, rs := range s.RootModule().Resources {
		if rs.Type != "opc_compute_security_association" {
			continue
		}

		input := compute.GetSecurityAssociationInput{
			Name: rs.Primary.Attributes["name"],
		}
		if info, err := client.GetSecurityAssociation(&input); err == nil {
			return fmt.Errorf("Security Association %s still exists: %#v", input.Name, info)
		}
	}

	return nil
}

var testAccSecurityAssociationBasic = `
resource "opc_compute_security_list" "test" {
  name                 = "acc-test-sec-ass-sec-list-%d"
  policy               = "PERMIT"
  outbound_cidr_policy = "DENY"
}

resource "opc_compute_instance" "test" {
  name        = "acc-test-sec-ass-instance-%d"
  label       = "Security Associations Test Instance"
  shape       = "oc3"
  image_list   = "/oracle/public/oel_6.7_apaas_16.4.5_1610211300"
}

resource "opc_compute_security_association" "test" {
  vcable  = "${opc_compute_instance.test.vcable}"
  seclist = "${opc_compute_security_list.test.name}"
}
`

var testAccSecurityAssociationComplete = `
resource "opc_compute_security_list" "test" {
  name                 = "acc-test-sec-ass-sec-list-%d"
  policy               = "PERMIT"
  outbound_cidr_policy = "DENY"
}

resource "opc_compute_instance" "test" {
  name        = "acc-test-sec-ass-instance-%d"
  label       = "Security Associations Test Instance"
  shape       = "oc3"
  image_list   = "/oracle/public/oel_6.7_apaas_16.4.5_1610211300"
}

resource "opc_compute_security_association" "test" {
  name    = "acc-test-sec-ass-%d"
  vcable  = "${opc_compute_instance.test.vcable}"
  seclist = "${opc_compute_security_list.test.name}"
}
`
<|endoftext|>


<commit_before>package node

import (
	"errors"
	"testing"
	"time"
)

func TestConnection(t *testing.T) {
	key1, _ := NewECDSAKey()
	key2, _ := NewECDSAKey()

	n1 := NewServer(key1)
	defer n1.Close()
	n2 := NewServer(key2)
	defer n2.Close()

	err := n1.Listen("127.0.0.1:16543")
	if err != nil {
		t.Fatalf("Error listening %v", err)
	}
	err = n2.Connect("127.0.0.1:16543")
	if err != nil {
		t.Fatalf("Error connecting %v", err)
	}
}

func WaitForReachable(n *Node, addr NodeAddress) error {
	tick := time.Tick(100 * time.Millisecond)
	timeout := time.After(10 * time.Second)
	for {
		select {
		case <-tick:
			if n.IsReachable(addr) {
				return nil
			}
		case <-timeout:
			return errors.New("Timed out waiting for node to be reachable")
		}
	}

}

func TestDataTransmission(t *testing.T) {
	key1, _ := NewECDSAKey()
	key2, _ := NewECDSAKey()

	n1 := NewServer(key1)
	defer n1.Close()
	n2 := NewServer(key2)
	defer n2.Close()
	err := n1.Listen("127.0.0.1:16544")
	if err != nil {
		t.Fatalf("Error listening %v", err)
	}
	err = n2.Connect("127.0.0.1:16544")
	if err != nil {
		t.Fatalf("Error connecting %v", err)
	}

	// Wait for n1 to know about n2
	err = WaitForReachable(n1.Node(), key2.PublicKey().Hash())
	if err != nil {
		t.Fatalf("Error waiting for information %v", err)
	}

	p2 := testPacket(key2.PublicKey().Hash())
	err = n1.Node().SendPacket(p2)
	if err != nil {
		t.Fatalf("Error sending packet: %v", err)
	}

	timeout := time.After(10 * time.Second)
	for found := false; found != true; {
		select {
		case <-n2.Node().Packets():
			found = true
			break
		case <-timeout:
			t.Fatal("Timeout waiting for packets")
		}
	}

}
<commit_msg>Test multiple data packets in server_test<commit_after>package node

import (
	"errors"
	"fmt"
	"testing"
	"time"
)

func TestConnection(t *testing.T) {
	key1, _ := NewECDSAKey()
	key2, _ := NewECDSAKey()

	n1 := NewServer(key1)
	defer n1.Close()
	n2 := NewServer(key2)
	defer n2.Close()

	err := n1.Listen("127.0.0.1:16543")
	if err != nil {
		t.Fatalf("Error listening %v", err)
	}
	err = n2.Connect("127.0.0.1:16543")
	if err != nil {
		t.Fatalf("Error connecting %v", err)
	}
}

func WaitForReachable(n *Node, addr NodeAddress) error {
	tick := time.Tick(100 * time.Millisecond)
	timeout := time.After(10 * time.Second)
	for {
		select {
		case <-tick:
			if n.IsReachable(addr) {
				return nil
			}
		case <-timeout:
			return errors.New("Timed out waiting for node to be reachable")
		}
	}

}

func TestDataTransmission(t *testing.T) {
	key1, _ := NewECDSAKey()
	key2, _ := NewECDSAKey()

	n1 := NewServer(key1)
	defer n1.Close()
	n2 := NewServer(key2)
	defer n2.Close()
	err := n1.Listen("127.0.0.1:16544")
	if err != nil {
		t.Fatalf("Error listening %v", err)
	}
	err = n2.Connect("127.0.0.1:16544")
	if err != nil {
		t.Fatalf("Error connecting %v", err)
	}

	// Wait for n1 to know about n2
	err = WaitForReachable(n1.Node(), key2.PublicKey().Hash())
	if err != nil {
		t.Fatalf("Error waiting for information %v", err)
	}

	for i := 0; i < 10; i++ {
		p2 := Packet{key2.PublicKey().Hash(), 3, fmt.Sprintf("test%d", i)}
		err = n1.Node().SendPacket(p2)
		if err != nil {
			t.Fatalf("Error sending packet: %v", err)
		}

		timeout := time.After(10 * time.Second)
		for found := false; found != true; {
			select {
			case <-n2.Node().Packets():
				found = true
				break
			case <-timeout:
				t.Fatal("Timeout waiting for packets")
			}
		}
	}
}
<|endoftext|>


<commit_before>package coordinator_test

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/mistifyio/acomm"
	"github.com/mistifyio/coordinator"
	"github.com/pborman/uuid"
	"github.com/stretchr/testify/suite"
)

func TestServer(t *testing.T) {
	suite.Run(t, new(ServerSuite))
}

type ServerSuite struct {
	suite.Suite
	config     *coordinator.Config
	configData *coordinator.ConfigData
	server     *coordinator.Server
}

func (s *ServerSuite) SetupSuite() {
	log.SetLevel(log.FatalLevel)

	socketDir, err := ioutil.TempDir("", "coordinatorTest-")
	s.Require().NoError(err, "failed to create socket dir")

	s.configData = &coordinator.ConfigData{
		SocketDir:      socketDir,
		ServiceName:    uuid.New(),
		ExternalPort:   45678,
		RequestTimeout: 5,
		LogLevel:       "fatal",
	}

	s.config, _, _, _, err = newConfig(true, false, s.configData)
	s.Require().NoError(err, "failed to create config")
	s.Require().NoError(s.config.LoadConfig(), "failed to load config")
}

func (s *ServerSuite) SetupTest() {
	var err error
	s.server, err = coordinator.NewServer(s.config)
	s.Require().NoError(err, "failed to create server")
	s.Require().NotNil(s.server, "failed to create server")
}

func (s *ServerSuite) TearDownSuite() {
	_ = os.RemoveAll(s.configData.SocketDir)
}

func (s *ServerSuite) TestNewServer() {
	configInvalid := coordinator.NewConfig(nil, nil)

	server, err := coordinator.NewServer(configInvalid)
	s.Nil(server, "should not create server with invalid config")
	s.Error(err, "should error with invalid config")
}

func (s *ServerSuite) TestStartHandleHTTPStop() {
	// Start
	if !s.NoError(s.server.Start(), "failed to start server") {
		return
	}
	time.Sleep(time.Second)

	// Stop
	defer s.server.Stop()

	// Handle request
	failed := make(chan struct{})
	handled := make(chan struct{})
	ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		close(handled)
	}))
	defer ts.Close()

	taskListener := s.createTaskListener(failed)
	if taskListener == nil {
		return
	}
	defer taskListener.Stop(0)

	req, _ := acomm.NewRequest("foobar", ts.URL, struct{}{}, nil, nil)
	coordinatorURL, _ := url.ParseRequestURI(fmt.Sprintf("http://localhost:%v", s.configData.ExternalPort))
	if !s.NoError(acomm.Send(coordinatorURL, req)) {
		return
	}

	success := false
	select {
	case <-handled:
		success = true
	case <-failed:
	}

	s.True(success)
}

func (s *ServerSuite) TestStartHandleUnixStop() {
	// Start
	if !s.NoError(s.server.Start(), "failed to start server") {
		return
	}
	time.Sleep(time.Second)

	// Stop
	defer s.server.Stop()

	// Handle request
	failed := make(chan struct{})
	handled := make(chan struct{})

	responseListener := acomm.NewUnixListener(filepath.Join(s.configData.SocketDir, "testResponse.sock"), 0)
	if !s.NoError(responseListener.Start(), "failed to start task listener") {
		return
	}
	defer responseListener.Stop(0)
	go func() {
		conn := responseListener.NextConn()
		if conn == nil {
			return
		}
		defer responseListener.DoneConn(conn)

		close(handled)
		time.Sleep(100 * time.Millisecond)
	}()

	taskListener := s.createTaskListener(failed)
	if taskListener == nil {
		return
	}
	defer taskListener.Stop(0)

	req, _ := acomm.NewRequest("foobar", responseListener.URL().String(), struct{}{}, nil, nil)
	coordinatorURL, _ := url.ParseRequestURI("unix://" + filepath.Join(s.config.SocketDir(), "coordinator", s.config.ServiceName()+".sock"))
	if !s.NoError(acomm.Send(coordinatorURL, req)) {
		return
	}

	success := false
	select {
	case <-handled:
		success = true
	case <-failed:
	}

	s.True(success)
}

func (s *ServerSuite) TestStopOnSignal() {
	selfProcess, err := os.FindProcess(os.Getpid())
	if !s.NoError(err, "couldn't find this process") {
		return
	}

	if !s.NoError(s.server.Start(), "failed to start server") {
		return
	}

	stopSignal := syscall.SIGUSR1

	done := make(chan struct{})
	go func() {
		s.server.StopOnSignal(stopSignal)
		close(done)
	}()

	time.Sleep(time.Second)
	_ = selfProcess.Signal(stopSignal)

	<-done
}

func (s *ServerSuite) createTaskListener(failed chan struct{}) *acomm.UnixListener {
	taskListener := acomm.NewUnixListener(filepath.Join(s.configData.SocketDir, "foobar", "test.sock"), 0)
	if !s.NoError(taskListener.Start(), "failed to start task listener") {
		return nil
	}

	go func() {
		conn := taskListener.NextConn()
		if conn == nil {
			return
		}
		defer taskListener.DoneConn(conn)

		req := &acomm.Request{}
		if err := acomm.UnmarshalConnData(conn, req); err != nil {
			close(failed)
			return
		}

		// Respond to the initial request
		resp, _ := acomm.NewResponse(req, nil, nil, nil)
		if err := acomm.SendConnData(conn, resp); err != nil {
			close(failed)
			return
		}

		// Response to hook
		resp, _ = acomm.NewResponse(req, nil, nil, nil)
		if err := req.Respond(resp); err != nil {
			close(failed)
			return
		}
	}()

	time.Sleep(time.Second)
	return taskListener
}
<commit_msg>Expand server tests<commit_after>package coordinator_test

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"path/filepath"
	"syscall"
	"testing"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/mistifyio/acomm"
	"github.com/mistifyio/coordinator"
	"github.com/pborman/uuid"
	"github.com/stretchr/testify/suite"
)

func TestServer(t *testing.T) {
	suite.Run(t, new(ServerSuite))
}

type ServerSuite struct {
	suite.Suite
	config     *coordinator.Config
	configData *coordinator.ConfigData
	server     *coordinator.Server
}

type params struct {
	ID string
}

func (s *ServerSuite) SetupSuite() {
	log.SetLevel(log.FatalLevel)

	socketDir, err := ioutil.TempDir("", "coordinatorTest-")
	s.Require().NoError(err, "failed to create socket dir")

	s.configData = &coordinator.ConfigData{
		SocketDir:      socketDir,
		ServiceName:    uuid.New(),
		ExternalPort:   45678,
		RequestTimeout: 5,
		LogLevel:       "fatal",
	}

	s.config, _, _, _, err = newConfig(true, false, s.configData)
	s.Require().NoError(err, "failed to create config")
	s.Require().NoError(s.config.LoadConfig(), "failed to load config")
}

func (s *ServerSuite) SetupTest() {
	var err error
	s.server, err = coordinator.NewServer(s.config)
	s.Require().NoError(err, "failed to create server")
	s.Require().NotNil(s.server, "failed to create server")
}

func (s *ServerSuite) TearDownSuite() {
	_ = os.RemoveAll(s.configData.SocketDir)
}

func (s *ServerSuite) TestNewServer() {
	configInvalid := coordinator.NewConfig(nil, nil)

	server, err := coordinator.NewServer(configInvalid)
	s.Nil(server, "should not create server with invalid config")
	s.Error(err, "should error with invalid config")
}

func (s *ServerSuite) TestReqRespHandle() {
	// Start
	if !s.NoError(s.server.Start(), "failed to start server") {
		return
	}
	time.Sleep(time.Second)
	// Stop
	defer s.server.Stop()

	// Set up handlers
	result := make(chan *params, 10)

	// Task handler
	taskName := "foobar"
	taskListener := s.createTaskListener(taskName, result)
	if taskListener == nil {
		return
	}
	defer taskListener.Stop(0)

	// Response handlers
	responseServer, responseListener := s.createResponseHandlers(result)
	if responseServer != nil {
		defer responseServer.Close()
	}
	if responseListener != nil {
		defer responseListener.Stop(0)
	}
	if responseServer == nil || responseListener == nil {
		return
	}

	// Coordinator URLs
	internalURL, _ := url.ParseRequestURI("unix://" + filepath.Join(
		s.config.SocketDir(),
		"coordinator",
		s.config.ServiceName()+".sock"),
	)
	externalURL, _ := url.ParseRequestURI(fmt.Sprintf(
		"http://localhost:%v",
		s.configData.ExternalPort),
	)

	// Test cases
	tests := []struct {
		description  string
		taskName     string
		internal     bool
		params       *params
		expectFailed bool
	}{
		{"valid http", taskName, false, &params{uuid.New()}, false},
		{"valid unix", taskName, true, &params{uuid.New()}, false},
		{"bad task http", "asdf", false, &params{uuid.New()}, true},
		{"bad task unix", "asdf", true, &params{uuid.New()}, true},
	}

	for _, test := range tests {
		msg := testMsgFunc(test.description)
		hookURL := responseServer.URL
		coordinatorURL := externalURL
		if test.internal {
			hookURL = responseListener.URL().String()
			coordinatorURL = internalURL
		}

		req, _ := acomm.NewRequest(test.taskName, hookURL, test.params, nil, nil)
		if err := acomm.Send(coordinatorURL, req); err != nil {
			result <- nil
		}

		respData := <-result
		if test.expectFailed {
			s.Nil(respData, msg("should have failed"))
		} else {
			s.Equal(test.params, respData, msg("should have gotten the correct response data"))
		}

		drainChan(result)
	}
}

func (s *ServerSuite) TestStopOnSignal() {
	selfProcess, err := os.FindProcess(os.Getpid())
	if !s.NoError(err, "couldn't find this process") {
		return
	}

	if !s.NoError(s.server.Start(), "failed to start server") {
		return
	}

	stopSignal := syscall.SIGUSR1

	done := make(chan struct{})
	go func() {
		s.server.StopOnSignal(stopSignal)
		close(done)
	}()

	time.Sleep(time.Second)
	_ = selfProcess.Signal(stopSignal)

	<-done
}

func (s *ServerSuite) createTaskListener(taskName string, result chan *params) *acomm.UnixListener {
	taskListener := acomm.NewUnixListener(filepath.Join(s.configData.SocketDir, taskName, "test.sock"), 0)
	if !s.NoError(taskListener.Start(), "failed to start task listener") {
		return nil
	}

	go func() {
		for {
			conn := taskListener.NextConn()
			if conn == nil {
				break
			}
			defer taskListener.DoneConn(conn)
			req := &acomm.Request{}
			if err := acomm.UnmarshalConnData(conn, req); err != nil {
				result <- nil
				continue
			}

			params := &params{}
			_ = req.UnmarshalArgs(params)

			// Respond to the initial request
			resp, _ := acomm.NewResponse(req, nil, nil, nil)
			if err := acomm.SendConnData(conn, resp); err != nil {
				result <- nil
				continue
			}

			// Response to hook
			resp, _ = acomm.NewResponse(req, req.Args, nil, nil)
			if err := req.Respond(resp); err != nil {
				result <- nil
				continue
			}
		}
	}()

	time.Sleep(time.Second)
	return taskListener
}

func (s *ServerSuite) createResponseHandlers(result chan *params) (*httptest.Server, *acomm.UnixListener) {
	// HTTP response
	responseServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		resp := &acomm.Response{}
		decoder := json.NewDecoder(r.Body)
		_ = decoder.Decode(resp)

		p := &params{}
		_ = resp.UnmarshalResult(p)
		result <- p
	}))

	// Unix response
	responseListener := acomm.NewUnixListener(filepath.Join(s.configData.SocketDir, "testResponse.sock"), 0)
	if !s.NoError(responseListener.Start(), "failed to start task listener") {
		return responseServer, nil
	}

	go func() {
		conn := responseListener.NextConn()
		if conn == nil {
			return
		}
		defer responseListener.DoneConn(conn)

		resp := &acomm.Response{}
		_ = acomm.UnmarshalConnData(conn, resp)
		p := &params{}
		_ = resp.UnmarshalResult(p)
		result <- p
	}()

	return responseServer, responseListener
}

// drainChan is a helper function to drain a channel, such as between test cases
func drainChan(ch chan *params) {
	for {
		select {
		case <-ch:
		default:
			return
		}
	}
}
<|endoftext|>


<commit_before>package raft

import (
	"testing"
)

//------------------------------------------------------------------------------
//
// Tests
//
//------------------------------------------------------------------------------

//--------------------------------------
// Request Vote
//--------------------------------------

// Ensure that we can request a vote from a server that has not voted.
func TestServerRequestVote(t *testing.T) {
	server := newTestServer("1")
	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 0, 0))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Invalid request vote response: %v/%v", resp.Term, resp.VoteGranted)
	}
}

// Ensure that a vote request is denied if it comes from an old term.
func TestServerRequestVoteDeniedForStaleTerm(t *testing.T) {
	server := newTestServer("1")
	server.state = Leader
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 0, 0))
	if !(resp.Term == 2 && !resp.VoteGranted) {
		t.Fatalf("Invalid request vote response: %v/%v", resp.Term, resp.VoteGranted)
	}
	if server.currentTerm != 2 && server.state != Follower {
		t.Fatalf("Server did not update term and demote: %v / %v", server.currentTerm, server.state)
	}
}

// Ensure that a vote request is denied if we've already voted for a different candidate.
func TestServerRequestVoteDeniedIfAlreadyVoted(t *testing.T) {
	server := newTestServer("1")
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(2, "foo", 0, 0))
	if !(resp.Term == 2 && resp.VoteGranted) {
		t.Fatalf("First vote should not have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(2, "bar", 0, 0))
	if !(resp.Term == 2 && !resp.VoteGranted) {
		t.Fatalf("Second vote should have been denied")
	}
}

// Ensure that a vote request is approved if vote occurs in a new term.
func TestServerRequestVoteApprovedIfAlreadyVotedInOlderTerm(t *testing.T) {
	server := newTestServer("1")
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(2, "foo", 0, 0))
	if !(resp.Term == 2 && resp.VoteGranted && server.votedFor == "foo") {
		t.Fatalf("First vote should not have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(3, "bar", 0, 0))
	if !(resp.Term == 3 && resp.VoteGranted && server.votedFor == "bar") {
		t.Fatalf("Second vote should have been approved")
	}
}

// Ensure that a vote request is denied if the log is out of date.
func TestServerRequestVoteDenyIfCandidateLogIsBehind(t *testing.T) {
	server := newTestServerWithLog("1",
		`cf4aab23 0000000000000001 0000000000000001 cmd_1 {"val":"foo","i":20}`+"\n"+
			`4c08d91f 0000000000000002 0000000000000001 cmd_2 {"x":100}`+"\n"+
			`6ac5807c 0000000000000003 0000000000000002 cmd_1 {"val":"bar","i":0}`+"\n")
	server.Start()

	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 2, 2))
	if !(resp.Term == 1 && !resp.VoteGranted) {
		t.Fatalf("Stale index vote should have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 3, 1))
	if !(resp.Term == 1 && !resp.VoteGranted) {
		t.Fatalf("Stale term vote should have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 3, 2))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Matching log vote should have been granted")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 4, 3))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Ahead-of-log vote should have been granted")
	}
}

//--------------------------------------
// Append Entries
//--------------------------------------

// Ensure we can append entries to a server.
func TestServerAppendEntries(t *testing.T) {
	server := newTestServer("1")
	server.Start()

	// Append single entry.
	entries := []*LogEntry{NewLogEntry(nil, 1, 1, &TestCommand1{"foo", 10})}
	resp, err := server.AppendEntries(NewAppendEntriesRequest(1, "ldr", 0, 0, entries, 0))
	if !(resp.Term == 1 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 0 && term == 0) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	// Append multiple entries + commit the last one.
	entries = []*LogEntry{NewLogEntry(nil, 2, 1, &TestCommand1{"bar", 20}), NewLogEntry(nil, 3, 1, &TestCommand1{"baz", 30})}
	resp, err = server.AppendEntries(NewAppendEntriesRequest(1, "ldr", 1, 1, entries, 1))
	if !(resp.Term == 1 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 1 && term == 1) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	// Send zero entries and commit everything.
	resp, err = server.AppendEntries(NewAppendEntriesRequest(2, "ldr", 3, 1, []*LogEntry{}, 3))
	if !(resp.Term == 2 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 3 && term == 1) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	server.Stop()
}

// TODO: Reject entries from old terms.
// TODO: Reject new entries to log if entries already exist.
// TODO: Reject entries from earlier index or term.
// TODO: Test rollback of uncommitted entries.

//--------------------------------------
// Membership
//--------------------------------------

// Ensure that we can start a single server and append to its log.
func TestServerSingleNode(t *testing.T) {
	server := newTestServer("1")
	if server.state != Stopped {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Get the server running.
	if err := server.Start(); err != nil {
		t.Fatalf("Unable to start server: %v", err)
	}
	if server.state != Follower {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Join the server to itself.
	if err := server.Join("1"); err != nil {
		t.Fatalf("Unable to join: %v", err)
	}
	if server.state != Leader {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Stop the server.
	server.Stop()
	if server.state != Stopped {
		t.Fatalf("Unexpected server state: %v", server.state)
	}
}

// Ensure that we can start multiple servers and determine a leader.
/*
func TestServerMultiNode(t *testing.T) {
	warn("== begin multi-node ==")

	// Initialize the servers.
	names := []string{"1", "2", "3"}
	servers := map[string]*Server{}
	for _, name := range names {
		server := newTestServer(name)
		server.DoHandler = func(server *Server, peer *Peer, command Command) error {
			return servers[peer.name].Do(command)
		}
		if err := server.Start(); err != nil {
			t.Fatalf("Unable to start server[%s]: %v", name, err)
		}
		if err := server.Join("1"); err != nil {
			t.Fatalf("Unable to join server[%s]: %v", name, err)
		}
		servers[name] = server
	}

	// Check that two peers exist on leader.
	leader := servers["1"]
	if leader.MemberCount() != 3 {
		t.Fatalf("Expected member count to be 2, got %v", leader.MemberCount())
	}

	// Stop the servers.
	for _, server := range servers {
		server.Stop()
	}
	warn("== end multi-node ==")
}
*/
<commit_msg>Add test for stale append.<commit_after>package raft

import (
	"testing"
)

//------------------------------------------------------------------------------
//
// Tests
//
//------------------------------------------------------------------------------

//--------------------------------------
// Request Vote
//--------------------------------------

// Ensure that we can request a vote from a server that has not voted.
func TestServerRequestVote(t *testing.T) {
	server := newTestServer("1")
	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 0, 0))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Invalid request vote response: %v/%v", resp.Term, resp.VoteGranted)
	}
}

// Ensure that a vote request is denied if it comes from an old term.
func TestServerRequestVoteDeniedForStaleTerm(t *testing.T) {
	server := newTestServer("1")
	server.state = Leader
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 0, 0))
	if !(resp.Term == 2 && !resp.VoteGranted) {
		t.Fatalf("Invalid request vote response: %v/%v", resp.Term, resp.VoteGranted)
	}
	if server.currentTerm != 2 && server.state != Follower {
		t.Fatalf("Server did not update term and demote: %v / %v", server.currentTerm, server.state)
	}
}

// Ensure that a vote request is denied if we've already voted for a different candidate.
func TestServerRequestVoteDeniedIfAlreadyVoted(t *testing.T) {
	server := newTestServer("1")
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(2, "foo", 0, 0))
	if !(resp.Term == 2 && resp.VoteGranted) {
		t.Fatalf("First vote should not have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(2, "bar", 0, 0))
	if !(resp.Term == 2 && !resp.VoteGranted) {
		t.Fatalf("Second vote should have been denied")
	}
}

// Ensure that a vote request is approved if vote occurs in a new term.
func TestServerRequestVoteApprovedIfAlreadyVotedInOlderTerm(t *testing.T) {
	server := newTestServer("1")
	server.currentTerm = 2
	resp := server.RequestVote(NewRequestVoteRequest(2, "foo", 0, 0))
	if !(resp.Term == 2 && resp.VoteGranted && server.votedFor == "foo") {
		t.Fatalf("First vote should not have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(3, "bar", 0, 0))
	if !(resp.Term == 3 && resp.VoteGranted && server.votedFor == "bar") {
		t.Fatalf("Second vote should have been approved")
	}
}

// Ensure that a vote request is denied if the log is out of date.
func TestServerRequestVoteDenyIfCandidateLogIsBehind(t *testing.T) {
	server := newTestServerWithLog("1",
		`cf4aab23 0000000000000001 0000000000000001 cmd_1 {"val":"foo","i":20}`+"\n"+
			`4c08d91f 0000000000000002 0000000000000001 cmd_2 {"x":100}`+"\n"+
			`6ac5807c 0000000000000003 0000000000000002 cmd_1 {"val":"bar","i":0}`+"\n")
	server.Start()

	resp := server.RequestVote(NewRequestVoteRequest(1, "foo", 2, 2))
	if !(resp.Term == 1 && !resp.VoteGranted) {
		t.Fatalf("Stale index vote should have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 3, 1))
	if !(resp.Term == 1 && !resp.VoteGranted) {
		t.Fatalf("Stale term vote should have been denied")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 3, 2))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Matching log vote should have been granted")
	}
	resp = server.RequestVote(NewRequestVoteRequest(1, "foo", 4, 3))
	if !(resp.Term == 1 && resp.VoteGranted) {
		t.Fatalf("Ahead-of-log vote should have been granted")
	}
}

//--------------------------------------
// Append Entries
//--------------------------------------

// Ensure we can append entries to a server.
func TestServerAppendEntries(t *testing.T) {
	server := newTestServer("1")
	server.Start()

	// Append single entry.
	entries := []*LogEntry{NewLogEntry(nil, 1, 1, &TestCommand1{"foo", 10})}
	resp, err := server.AppendEntries(NewAppendEntriesRequest(1, "ldr", 0, 0, entries, 0))
	if !(resp.Term == 1 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 0 && term == 0) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	// Append multiple entries + commit the last one.
	entries = []*LogEntry{NewLogEntry(nil, 2, 1, &TestCommand1{"bar", 20}), NewLogEntry(nil, 3, 1, &TestCommand1{"baz", 30})}
	resp, err = server.AppendEntries(NewAppendEntriesRequest(1, "ldr", 1, 1, entries, 1))
	if !(resp.Term == 1 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 1 && term == 1) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	// Send zero entries and commit everything.
	resp, err = server.AppendEntries(NewAppendEntriesRequest(2, "ldr", 3, 1, []*LogEntry{}, 3))
	if !(resp.Term == 2 && resp.Success && err == nil) {
		t.Fatalf("AppendEntries failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 3 && term == 1) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}

	server.Stop()
}

// Ensure that entries with stale terms are rejected.
func TestServerAppendEntriesWithStaleTermsAreRejected(t *testing.T) {
	server := newTestServer("1")
	server.Start()
	server.currentTerm = 2

	// Append single entry.
	entries := []*LogEntry{NewLogEntry(nil, 1, 1, &TestCommand1{"foo", 10})}
	resp, err := server.AppendEntries(NewAppendEntriesRequest(1, "ldr", 0, 0, entries, 0))
	if !(resp.Term == 2 && !resp.Success && err != nil && err.Error() == "raft.Server: Stale request term") {
		t.Fatalf("AppendEntries should have failed: %v/%v : %v", resp.Term, resp.Success, err)
	}
	if index, term := server.log.CommitInfo(); !(index == 0 && term == 0) {
		t.Fatalf("Invalid commit info [IDX=%v, TERM=%v]", index, term)
	}
}

// TODO: Reject new entries to log if entries already exist.
// TODO: Reject entries from earlier index or term.
// TODO: Test rollback of uncommitted entries.

//--------------------------------------
// Membership
//--------------------------------------

// Ensure that we can start a single server and append to its log.
func TestServerSingleNode(t *testing.T) {
	server := newTestServer("1")
	if server.state != Stopped {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Get the server running.
	if err := server.Start(); err != nil {
		t.Fatalf("Unable to start server: %v", err)
	}
	if server.state != Follower {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Join the server to itself.
	if err := server.Join("1"); err != nil {
		t.Fatalf("Unable to join: %v", err)
	}
	if server.state != Leader {
		t.Fatalf("Unexpected server state: %v", server.state)
	}

	// Stop the server.
	server.Stop()
	if server.state != Stopped {
		t.Fatalf("Unexpected server state: %v", server.state)
	}
}

// Ensure that we can start multiple servers and determine a leader.
/*
func TestServerMultiNode(t *testing.T) {
	warn("== begin multi-node ==")

	// Initialize the servers.
	names := []string{"1", "2", "3"}
	servers := map[string]*Server{}
	for _, name := range names {
		server := newTestServer(name)
		server.DoHandler = func(server *Server, peer *Peer, command Command) error {
			return servers[peer.name].Do(command)
		}
		if err := server.Start(); err != nil {
			t.Fatalf("Unable to start server[%s]: %v", name, err)
		}
		if err := server.Join("1"); err != nil {
			t.Fatalf("Unable to join server[%s]: %v", name, err)
		}
		servers[name] = server
	}

	// Check that two peers exist on leader.
	leader := servers["1"]
	if leader.MemberCount() != 3 {
		t.Fatalf("Expected member count to be 2, got %v", leader.MemberCount())
	}

	// Stop the servers.
	for _, server := range servers {
		server.Stop()
	}
	warn("== end multi-node ==")
}
*/
<|endoftext|>