<commit_before>package class

import (
	"fmt"
	. "jvmgo/any"
	"jvmgo/classfile"
	"jvmgo/classpath"
	"jvmgo/jvm/jerrors"
	"jvmgo/jvm/options"
)

const (
	jlObjectClassName       = "java/lang/Object"
	jlClassClassName        = "java/lang/Class"
	jlCloneableClassName    = "java/lang/Cloneable"
	ioSerializableClassName = "java/io/Serializable"
	jlThreadClassName       = "java/lang/Thread"
	jlStringClassName       = "java/lang/String"
)

var (
	_jlObjectClass       *Class
	_jlClassClass        *Class
	_jlCloneableClass    *Class
	_ioSerializableClass *Class
	_jlThreadClass       *Class
	_jlStringClass       *Class
)

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/

// the bootstrap class loader
type ClassLoader struct {
	classPath *classpath.ClassPath
	classMap  map[string]*Class
}

func NewClassLoader(cp *classpath.ClassPath) *ClassLoader {
	classMap := map[string]*Class{}
	loader := &ClassLoader{cp, classMap}
	loader._init()
	return loader
}

// ClassLoaderGetter
func (self *ClassLoader) ClassLoader() *ClassLoader {
	return self
}
func (self *ClassLoader) ClassPath() *classpath.ClassPath {
	return self.classPath
}

func (self *ClassLoader) _init() {
	_jlObjectClass = self.LoadClass(jlObjectClassName)
	_jlClassClass = self.LoadClass(jlClassClassName)
	for _, class := range self.classMap {
		if class.jClass == nil {
			class.jClass = _jlClassClass.NewObj()
			class.jClass.extra = class
		}
	}
	_jlCloneableClass = self.LoadClass(jlCloneableClassName)
	_ioSerializableClass = self.LoadClass(ioSerializableClassName)
	_jlThreadClass = self.LoadClass(jlThreadClassName)
	_jlStringClass = self.LoadClass(jlStringClassName)
	self.loadPrimitiveClasses()
	self.loadPrimitiveArrayClasses()
}

func (self *ClassLoader) loadPrimitiveClasses() {
	for primitiveType, _ := range primitiveTypes {
		self.loadPrimitiveClass(primitiveType)
	}
}
func (self *ClassLoader) loadPrimitiveClass(className string) {
	class := &Class{name: className}
	class.classLoader = self
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	class.MarkInitialized()
	self.classMap[className] = class
}

func (self *ClassLoader) loadPrimitiveArrayClasses() {
	for _, descriptor := range primitiveTypes {
		self.loadArrayClass("[" + descriptor)
	}
}
func (self *ClassLoader) loadArrayClass(className string) *Class {
	class := &Class{name: className}
	class.classLoader = self
	class.superClass = _jlObjectClass
	class.interfaces = []*Class{_jlCloneableClass, _ioSerializableClass}
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	createVtable(class)
	class.MarkInitialized()
	self.classMap[className] = class
	return class
}

func (self *ClassLoader) getRefArrayClass(componentClass *Class) *Class {
	arrClassName := "[L" + componentClass.Name() + ";"
	return self._getRefArrayClass(arrClassName)
}
func (self *ClassLoader) _getRefArrayClass(arrClassName string) *Class {
	if arrClass, ok := self.classMap[arrClassName]; ok {
		return arrClass
	}
	return self.loadArrayClass(arrClassName)
}

// todo
func (self *ClassLoader) GetPrimitiveClass(name string) *Class {
	return self.getClass(name)
}

func (self *ClassLoader) JLObjectClass() *Class {
	return _jlObjectClass
}
func (self *ClassLoader) JLClassClass() *Class {
	return _jlClassClass
}
func (self *ClassLoader) JLStringClass() *Class {
	return _jlStringClass
}
func (self *ClassLoader) JLThreadClass() *Class {
	return _jlThreadClass
}

// todo dangerous
func (self *ClassLoader) getClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		return class
	}
	panic("class not loaded! " + name)
}

func (self *ClassLoader) LoadClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		// already loaded
		return class
	} else if name[0] == '[' {
		// array class
		return self._getRefArrayClass(name)
	} else {
		return self.reallyLoadClass(name)
	}
}

func (self *ClassLoader) reallyLoadClass(name string) *Class {
	cpEntry, data := self.readClassData(name)
	class := self._loadClass(name, data)
	class.loadedFrom = cpEntry

	if options.VerboseClass {
		fmt.Printf("[Loaded %s from %s]\n", name, cpEntry)
	}

	return class
}

func (self *ClassLoader) readClassData(name string) (classpath.ClassPathEntry, []byte) {
	cpEntry, classData, err := self.classPath.ReadClassData(name)
	if err != nil {
		panic(jerrors.NewClassNotFoundError(SlashToDot(name)))
	}

	return cpEntry, classData
}

func (self *ClassLoader) parseClassData(name string, data []byte) *Class {
	cf, err := classfile.ParseClassFile(data)
	if err != nil {
		// todo
		panic("failed to parse class file: " + name + "!" + err.Error())
	}

	return newClass(cf)
}

func (self *ClassLoader) _loadClass(name string, data []byte) *Class {
	class := self.parseClassData(name, data)
	hackClass(class)
	self.resolveSuperClass(class)
	self.resolveInterfaces(class)
	calcStaticFieldSlots(class)
	calcInstanceFieldSlots(class)
	createVtable(class)
	prepare(class)
	// todo
	class.classLoader = self
	self.classMap[name] = class

	if _jlClassClass != nil {
		class.jClass = _jlClassClass.NewObj()
		class.jClass.extra = class
	}

	return class
}

// todo
func hackClass(class *Class) {
	if class.name == "java/lang/ClassLoader" {
		loadLibrary := class.GetStaticMethod("loadLibrary", "(Ljava/lang/Class;Ljava/lang/String;Z)V")
		loadLibrary.code = []byte{0xb1} // return void
	}
}

// todo
func (self *ClassLoader) resolveSuperClass(class *Class) {
	if class.superClassName != "" {
		class.superClass = self.LoadClass(class.superClassName)
	}
}
func (self *ClassLoader) resolveInterfaces(class *Class) {
	interfaceCount := len(class.interfaceNames)
	if interfaceCount > 0 {
		class.interfaces = make([]*Class, interfaceCount)
		for i, interfaceName := range class.interfaceNames {
			class.interfaces[i] = self.LoadClass(interfaceName)
		}
	}
}

func calcStaticFieldSlots(class *Class) {
	slotId := uint(0)
	for _, field := range class.fields {
		if field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.staticFieldCount = slotId
}

func calcInstanceFieldSlots(class *Class) {
	slotId := uint(0)
	if class.superClassName != "" {
		slotId = class.superClass.instanceFieldCount
	}
	for _, field := range class.fields {
		if !field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.instanceFieldCount = slotId
}

func prepare(class *Class) {
	class.staticFieldValues = make([]Any, class.staticFieldCount)
	for _, field := range class.fields {
		if field.IsStatic() {
			class.staticFieldValues[field.slot] = field.defaultValue()
		}
	}
}

// todo
func (self *ClassLoader) DefineClass(name string, data []byte) *Class {
	return self._loadClass(name, data)
}
<commit_msg>!!!!!!!!!!redesign bootstrap classloader!!!!!!!!!!<commit_after>package class

import (
	"fmt"
	. "jvmgo/any"
	"jvmgo/classfile"
	"jvmgo/classpath"
	"jvmgo/jvm/jerrors"
	"jvmgo/jvm/options"
)

const (
	jlObjectClassName       = "java/lang/Object"
	jlClassClassName        = "java/lang/Class"
	jlCloneableClassName    = "java/lang/Cloneable"
	ioSerializableClassName = "java/io/Serializable"
	jlThreadClassName       = "java/lang/Thread"
	jlStringClassName       = "java/lang/String"
)

var (
	_bootLoader          *ClassLoader // bootstrap class loader
	_jlObjectClass       *Class
	_jlClassClass        *Class
	_jlCloneableClass    *Class
	_ioSerializableClass *Class
	_jlThreadClass       *Class
	_jlStringClass       *Class
)

/*
class names:
    - primitive types: boolean, byte, int ...
    - primitive arrays: [Z, [B, [I ...
    - non-array classes: java/lang/Object ...
    - array classes: [Ljava/lang/Object; ...
*/

// the bootstrap class loader
type ClassLoader struct {
	classPath *classpath.ClassPath
	classMap  map[string]*Class
}

func BootLoader() *ClassLoader {
	return _bootLoader
}

func InitBootLoader(cp *classpath.ClassPath) {
	_bootLoader = &ClassLoader{
		classPath: cp,
		classMap:  map[string]*Class{},
	}
	_bootLoader._init()
}

// todo
func NewClassLoader(cp *classpath.ClassPath) *ClassLoader {
	InitBootLoader(cp)
	return BootLoader()
}

// ClassLoaderGetter
func (self *ClassLoader) ClassLoader() *ClassLoader {
	return self
}
func (self *ClassLoader) ClassPath() *classpath.ClassPath {
	return self.classPath
}

func (self *ClassLoader) _init() {
	_jlObjectClass = self.LoadClass(jlObjectClassName)
	_jlClassClass = self.LoadClass(jlClassClassName)
	for _, class := range self.classMap {
		if class.jClass == nil {
			class.jClass = _jlClassClass.NewObj()
			class.jClass.extra = class
		}
	}
	_jlCloneableClass = self.LoadClass(jlCloneableClassName)
	_ioSerializableClass = self.LoadClass(ioSerializableClassName)
	_jlThreadClass = self.LoadClass(jlThreadClassName)
	_jlStringClass = self.LoadClass(jlStringClassName)
	self.loadPrimitiveClasses()
	self.loadPrimitiveArrayClasses()
}

func (self *ClassLoader) loadPrimitiveClasses() {
	for primitiveType, _ := range primitiveTypes {
		self.loadPrimitiveClass(primitiveType)
	}
}
func (self *ClassLoader) loadPrimitiveClass(className string) {
	class := &Class{name: className}
	class.classLoader = self
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	class.MarkInitialized()
	self.classMap[className] = class
}

func (self *ClassLoader) loadPrimitiveArrayClasses() {
	for _, descriptor := range primitiveTypes {
		self.loadArrayClass("[" + descriptor)
	}
}
func (self *ClassLoader) loadArrayClass(className string) *Class {
	class := &Class{name: className}
	class.classLoader = self
	class.superClass = _jlObjectClass
	class.interfaces = []*Class{_jlCloneableClass, _ioSerializableClass}
	class.jClass = _jlClassClass.NewObj()
	class.jClass.extra = class
	createVtable(class)
	class.MarkInitialized()
	self.classMap[className] = class
	return class
}

func (self *ClassLoader) getRefArrayClass(componentClass *Class) *Class {
	arrClassName := "[L" + componentClass.Name() + ";"
	return self._getRefArrayClass(arrClassName)
}
func (self *ClassLoader) _getRefArrayClass(arrClassName string) *Class {
	if arrClass, ok := self.classMap[arrClassName]; ok {
		return arrClass
	}
	return self.loadArrayClass(arrClassName)
}

// todo
func (self *ClassLoader) GetPrimitiveClass(name string) *Class {
	return self.getClass(name)
}

func (self *ClassLoader) JLObjectClass() *Class {
	return _jlObjectClass
}
func (self *ClassLoader) JLClassClass() *Class {
	return _jlClassClass
}
func (self *ClassLoader) JLStringClass() *Class {
	return _jlStringClass
}
func (self *ClassLoader) JLThreadClass() *Class {
	return _jlThreadClass
}

// todo dangerous
func (self *ClassLoader) getClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		return class
	}
	panic("class not loaded! " + name)
}

func (self *ClassLoader) LoadClass(name string) *Class {
	if class, ok := self.classMap[name]; ok {
		// already loaded
		return class
	} else if name[0] == '[' {
		// array class
		return self._getRefArrayClass(name)
	} else {
		return self.reallyLoadClass(name)
	}
}

func (self *ClassLoader) reallyLoadClass(name string) *Class {
	cpEntry, data := self.readClassData(name)
	class := self._loadClass(name, data)
	class.loadedFrom = cpEntry

	if options.VerboseClass {
		fmt.Printf("[Loaded %s from %s]\n", name, cpEntry)
	}

	return class
}

func (self *ClassLoader) readClassData(name string) (classpath.ClassPathEntry, []byte) {
	cpEntry, classData, err := self.classPath.ReadClassData(name)
	if err != nil {
		panic(jerrors.NewClassNotFoundError(SlashToDot(name)))
	}

	return cpEntry, classData
}

func (self *ClassLoader) parseClassData(name string, data []byte) *Class {
	cf, err := classfile.ParseClassFile(data)
	if err != nil {
		// todo
		panic("failed to parse class file: " + name + "!" + err.Error())
	}

	return newClass(cf)
}

func (self *ClassLoader) _loadClass(name string, data []byte) *Class {
	class := self.parseClassData(name, data)
	hackClass(class)
	self.resolveSuperClass(class)
	self.resolveInterfaces(class)
	calcStaticFieldSlots(class)
	calcInstanceFieldSlots(class)
	createVtable(class)
	prepare(class)
	// todo
	class.classLoader = self
	self.classMap[name] = class

	if _jlClassClass != nil {
		class.jClass = _jlClassClass.NewObj()
		class.jClass.extra = class
	}

	return class
}

// todo
func hackClass(class *Class) {
	if class.name == "java/lang/ClassLoader" {
		loadLibrary := class.GetStaticMethod("loadLibrary", "(Ljava/lang/Class;Ljava/lang/String;Z)V")
		loadLibrary.code = []byte{0xb1} // return void
	}
}

// todo
func (self *ClassLoader) resolveSuperClass(class *Class) {
	if class.superClassName != "" {
		class.superClass = self.LoadClass(class.superClassName)
	}
}
func (self *ClassLoader) resolveInterfaces(class *Class) {
	interfaceCount := len(class.interfaceNames)
	if interfaceCount > 0 {
		class.interfaces = make([]*Class, interfaceCount)
		for i, interfaceName := range class.interfaceNames {
			class.interfaces[i] = self.LoadClass(interfaceName)
		}
	}
}

func calcStaticFieldSlots(class *Class) {
	slotId := uint(0)
	for _, field := range class.fields {
		if field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.staticFieldCount = slotId
}

func calcInstanceFieldSlots(class *Class) {
	slotId := uint(0)
	if class.superClassName != "" {
		slotId = class.superClass.instanceFieldCount
	}
	for _, field := range class.fields {
		if !field.IsStatic() {
			field.slot = slotId
			slotId++
		}
	}
	class.instanceFieldCount = slotId
}

func prepare(class *Class) {
	class.staticFieldValues = make([]Any, class.staticFieldCount)
	for _, field := range class.fields {
		if field.IsStatic() {
			class.staticFieldValues[field.slot] = field.defaultValue()
		}
	}
}

// todo
func (self *ClassLoader) DefineClass(name string, data []byte) *Class {
	return self._loadClass(name, data)
}
<|endoftext|>


<commit_before>package models

import (
	"testing"
	"time"

	"appengine/aetest"

	"github.com/santiaago/purple-wing/helpers/log"
)

func TestCreateTournament(t *testing.T) {
	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Create Tournament")

	tests := []struct {
		name       string
		tournament Tournament
		want       *Tournament
	}{
		{
			name: "Simple create",
			tournament: Tournament{
				Name:        "Foo",
				Description: "Foo description",
				Start:       time.Now(),
				End:         time.Now(),
				AdminId:     int64(0),
			},
			want: &Tournament{
				Name:            "Foo",
				Description:     "Foo description",
				Start:           time.Now(),
				End:             time.Now(),
				AdminId:         int64(0),
				GroupIds:        make([]int64, 0),
				Matches1stStage: make([]int64, 0),
				Matches2ndStage: make([]int64, 0),
				UserIds:         make([]int64, 0),
				TeamIds:         make([]int64, 0),
			},
		},
	}
	for _, test := range tests {
		got, _ := CreateTournament(c, test.tournament.Name, test.tournament.Description, test.tournament.Start, test.tournament.End, test.tournament.AdminId)
		if got == nil && test.want != nil {
			t.Errorf("TestCreateTournament(%q): got nil wanted %v", test.name, *test.want)
		} else if got != nil && test.want == nil {
			t.Errorf("TestCreateTournament(%q): got %v wanted nil", test.name, *got)
		} else if got == nil && test.want == nil {
			// This is OK
		} else if got.Name != test.want.Name ||
			got.Description != test.want.Description ||
			got.AdminId != test.want.AdminId ||
			len(got.GroupIds) != len(test.want.GroupIds) ||
			len(got.Matches1stStage) != len(test.want.Matches1stStage) ||
			len(got.Matches2ndStage) != len(test.want.Matches2ndStage) ||
			len(got.UserIds) != len(test.want.UserIds) ||
			len(got.TeamIds) != len(test.want.TeamIds) {
			t.Errorf("TestCreateTournament(%q): got %v wanted %v", test.name, *got, *test.want)
		}
	}
}

func TestDestroyTournament(t *testing.T) {
	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Destory Tournament")

	test := struct {
		name       string
		tournament Tournament
		want       *Tournament
	}{
		name: "destroy tournament",
		tournament: Tournament{
			Name:        "Foo",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		want: nil,
	}

	// create tournament
	tournament, _ := CreateTournament(c, test.tournament.Name, test.tournament.Description, test.tournament.Start, test.tournament.End, test.tournament.AdminId)

	// destory it
	if got := tournament.Destroy(c); got != nil {
		t.Errorf("TestDestroyTournament(%q): got %v wanted %v", test.name, got, test.want)
	}

	// make a query on datastore to be sure it is not there.
	if got, err1 := TournamentById(c, tournament.Id); err1 == nil {
		t.Errorf("TestDestroyTournament(%q): got %v wanted %v", test.name, got, test.want)
	}
}

func TestFindTournaments(t *testing.T) {

	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Find Tournament")

	tournaments := []Tournament{
		Tournament{
			Name:        "bar",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		Tournament{
			Name:        "foobar",
			Description: "foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		Tournament{
			Name:        "foobarfoo",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
	}

	tests := []struct {
		name        string
		tournaments []Tournament
		queries     []string
		want        struct {
			Len   int
			Names []string
		}
	}{
		{
			name:        "find tournaments in empty datastore",
			tournaments: make([]Tournament, 0),
			queries:     []string{"foo"},
			want: struct {
				Len   int
				Names []string
			}{
				Len:   int(0),
				Names: make([]string, 0),
			},
		},
		{
			name:        "find tournaments in full datastore",
			tournaments: tournaments,
			queries:     []string{"bar", "foobar", "foobarfoo"},
			want: struct {
				Len   int
				Names []string
			}{
				Len:   int(1),
				Names: []string{"bar", "foobar", "foobarfoo"},
			},
		},
	}
	// create tournaments
	for _, test := range tests {
		for i, _ := range test.tournaments {
			if _, err := CreateTournament(c, test.tournaments[i].Name, test.tournaments[i].Description, test.tournaments[i].Start, test.tournaments[i].End, test.tournaments[i].AdminId); err != nil {
				t.Errorf("TestFindTournaments(%q): error creating tournaments", test.name)

			}
		}
	}

	// search tournaments
	for _, test := range tests {
		for _, query := range test.queries {
			got := FindTournaments(c, "KeyName", query)
			if test.want.Len != len(got) {
				t.Errorf("TestFindTournaments(%q): got array of %v  wanted %v: query:%v t:%v", test.name, len(got), test.want.Len, query, got)
			}
			for _, tour := range got {
				var a arrayOfStrings
				a = test.want.Names
				if !a.Contains(tour.Name) {
					t.Errorf("TestFindTournaments(%q): name not found. got %v  wanted among %v", test.name, tour.Name, test.want.Names)
				}
			}
		}
	}
}

type arrayOfStrings []string

func (a arrayOfStrings) Contains(s string) bool {
	for _, e := range a {
		if e == s {
			return true
		}
	}
	return false
}
<commit_msg>finaly made that test pass! involves #308<commit_after>package models

import (
	"testing"
	"time"

	"appengine/aetest"
	"appengine/datastore"

	"github.com/santiaago/purple-wing/helpers/log"
)

func TestCreateTournament(t *testing.T) {
	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Create Tournament")

	tests := []struct {
		name       string
		tournament Tournament
		want       *Tournament
	}{
		{
			name: "Simple create",
			tournament: Tournament{
				Name:        "Foo",
				Description: "Foo description",
				Start:       time.Now(),
				End:         time.Now(),
				AdminId:     int64(0),
			},
			want: &Tournament{
				Name:            "Foo",
				Description:     "Foo description",
				Start:           time.Now(),
				End:             time.Now(),
				AdminId:         int64(0),
				GroupIds:        make([]int64, 0),
				Matches1stStage: make([]int64, 0),
				Matches2ndStage: make([]int64, 0),
				UserIds:         make([]int64, 0),
				TeamIds:         make([]int64, 0),
			},
		},
	}
	for _, test := range tests {
		got, _ := CreateTournament(c, test.tournament.Name, test.tournament.Description, test.tournament.Start, test.tournament.End, test.tournament.AdminId)
		if got == nil && test.want != nil {
			t.Errorf("TestCreateTournament(%q): got nil wanted %v", test.name, *test.want)
		} else if got != nil && test.want == nil {
			t.Errorf("TestCreateTournament(%q): got %v wanted nil", test.name, *got)
		} else if got == nil && test.want == nil {
			// This is OK
		} else if got.Name != test.want.Name ||
			got.Description != test.want.Description ||
			got.AdminId != test.want.AdminId ||
			len(got.GroupIds) != len(test.want.GroupIds) ||
			len(got.Matches1stStage) != len(test.want.Matches1stStage) ||
			len(got.Matches2ndStage) != len(test.want.Matches2ndStage) ||
			len(got.UserIds) != len(test.want.UserIds) ||
			len(got.TeamIds) != len(test.want.TeamIds) {
			t.Errorf("TestCreateTournament(%q): got %v wanted %v", test.name, *got, *test.want)
		}
	}
}

func TestDestroyTournament(t *testing.T) {
	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Destory Tournament")

	test := struct {
		name       string
		tournament Tournament
		want       *Tournament
	}{
		name: "destroy tournament",
		tournament: Tournament{
			Name:        "Foo",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		want: nil,
	}

	// create tournament
	tournament, _ := CreateTournament(c, test.tournament.Name, test.tournament.Description, test.tournament.Start, test.tournament.End, test.tournament.AdminId)

	// perform a get query so that the results of the unapplied write are visible to subsequent global queries.
	dummy := Tournament{}
	key := TournamentKeyById(c, tournament.Id)
	if err := datastore.Get(c, key, &dummy); err != nil {
		t.Fatal(err)
	}

	// destory it
	if got := tournament.Destroy(c); got != nil {
		t.Errorf("TestDestroyTournament(%q): got %v wanted %v", test.name, got, test.want)
	}

	// make a query on datastore to be sure it is not there.
	if got, err1 := TournamentById(c, tournament.Id); err1 == nil {
		t.Errorf("TestDestroyTournament(%q): got %v wanted %v", test.name, got, test.want)
	}
}

func TestFindTournaments(t *testing.T) {

	c, err := aetest.NewContext(nil)
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	log.Infof(c, "Test Find Tournament")

	tournaments := []Tournament{
		Tournament{
			Name:        "bar",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		Tournament{
			Name:        "foobar",
			Description: "foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
		Tournament{
			Name:        "foobarfoo",
			Description: "Foo description",
			Start:       time.Now(),
			End:         time.Now(),
			AdminId:     int64(0),
		},
	}

	tests := []struct {
		name        string
		tournaments []Tournament
		queries     []string
		want        struct {
			Len   int
			Names []string
		}
	}{
		{
			name:        "find tournaments in empty datastore",
			tournaments: make([]Tournament, 0),
			queries:     []string{"foo"},
			want: struct {
				Len   int
				Names []string
			}{
				Len:   int(0),
				Names: make([]string, 0),
			},
		},
		{
			name:        "find tournaments in full datastore",
			tournaments: tournaments,
			queries:     []string{"bar", "foobar", "foobarfoo"},
			want: struct {
				Len   int
				Names []string
			}{
				Len:   int(1),
				Names: []string{"bar", "foobar", "foobarfoo"},
			},
		},
	}

	// create tournaments
	for _, test := range tests {
		for i, _ := range test.tournaments {
			if got, err1 := CreateTournament(
				c,
				test.tournaments[i].Name,
				test.tournaments[i].Description,
				test.tournaments[i].Start,
				test.tournaments[i].End,
				test.tournaments[i].AdminId); err1 != nil {
				t.Errorf("TestFindTournaments(%q): error creating tournaments: %v", test.name, err1)
			} else {
				// perform a get query so that the results of the unapplied write are visible to subsequent global queries.
				dummy := Tournament{}
				key := TournamentKeyById(c, got.Id)
				if err := datastore.Get(c, key, &dummy); err != nil {
					t.Fatal(err)
				}
			}
		}
	}

	// search tournaments
	log.Infof(c, "Test Find Tournament: start search ok tournaments")
	for _, test := range tests {
		for _, query := range test.queries {
			got := FindTournaments(c, "KeyName", query)
			if test.want.Len != len(got) {
				t.Errorf("TestFindTournaments(%q): got array of %v  wanted %v: query:%v t:%v", test.name, len(got), test.want.Len, query, got)
			}
			for _, tour := range got {
				var a arrayOfStrings
				a = test.want.Names
				if !a.Contains(tour.Name) {
					t.Errorf("TestFindTournaments(%q): name not found. got %v  wanted among %v", test.name, tour.Name, test.want.Names)
				}
			}
		}
	}
}

type arrayOfStrings []string

func (a arrayOfStrings) Contains(s string) bool {
	for _, e := range a {
		if e == s {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>package app

import (
	"database/sql"
	"io/ioutil"

	"github.com/gin-gonic/gin"
	"github.com/osuripple/api/common"
)

// Method wraps an API method to a HandlerFunc.
func Method(f func(md common.MethodData) common.Response, db *sql.DB, privilegesNeeded ...int) gin.HandlerFunc {
	return func(c *gin.Context) {
		data, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.Error(err)
		}
		c.Request.Body.Close()

		token := ""
		switch {
		case c.Request.Header.Get("X-Ripple-Token") != "":
			token = c.Request.Header.Get("X-Ripple-Token")
		case c.Query("token") != "":
			token = c.Query("token")
		case c.Query("k") != "":
			token = c.Query("k")
		}

		md := common.MethodData{
			DB:          db,
			RequestData: data,
			C:           c,
		}
		if token != "" {
			tokenReal, exists := GetTokenFull(token, db)
			if exists {
				md.User = tokenReal
			}
		}

		missingPrivileges := 0
		for _, privilege := range privilegesNeeded {
			if int(md.User.Privileges)&privilege == 0 {
				missingPrivileges |= privilege
			}
		}
		if missingPrivileges != 0 {
			c.IndentedJSON(401, common.Response{
				Code:    401,
				Message: "You don't have the privilege(s): " + common.Privileges(missingPrivileges).String() + ".",
			})
			return
		}

		resp := f(md)
		if resp.Code == 0 {
			c.IndentedJSON(500, resp)
		} else {
			c.IndentedJSON(200, resp)
		}
	}
}
<commit_msg>Use resp.Code as the HTTP response code, unless the client has specifield they want 200-codes only.<commit_after>package app

import (
	"database/sql"
	"io/ioutil"

	"github.com/gin-gonic/gin"
	"github.com/osuripple/api/common"
)

// Method wraps an API method to a HandlerFunc.
func Method(f func(md common.MethodData) common.Response, db *sql.DB, privilegesNeeded ...int) gin.HandlerFunc {
	return func(c *gin.Context) {
		data, err := ioutil.ReadAll(c.Request.Body)
		if err != nil {
			c.Error(err)
		}
		c.Request.Body.Close()

		token := ""
		switch {
		case c.Request.Header.Get("X-Ripple-Token") != "":
			token = c.Request.Header.Get("X-Ripple-Token")
		case c.Query("token") != "":
			token = c.Query("token")
		case c.Query("k") != "":
			token = c.Query("k")
		}

		md := common.MethodData{
			DB:          db,
			RequestData: data,
			C:           c,
		}
		if token != "" {
			tokenReal, exists := GetTokenFull(token, db)
			if exists {
				md.User = tokenReal
			}
		}

		missingPrivileges := 0
		for _, privilege := range privilegesNeeded {
			if int(md.User.Privileges)&privilege == 0 {
				missingPrivileges |= privilege
			}
		}
		if missingPrivileges != 0 {
			c.IndentedJSON(401, common.Response{
				Code:    401,
				Message: "You don't have the privilege(s): " + common.Privileges(missingPrivileges).String() + ".",
			})
			return
		}

		resp := f(md)
		if resp.Code == 0 {
			resp.Code = 500
		}
		if _, exists := c.GetQuery("pls200"); exists {
			c.IndentedJSON(200, resp)
		} else {
			c.IndentedJSON(resp.Code, resp)
		}
	}
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

// HueBridge represents the Philips Hue bridge in
// your system.
// It is used to communicate with all devices.
type HueBridge struct {
	bridge   hue.Bridge
	BridgeIP string
	Username string
	Version  int
}

const hueBridgeAppName = "kelvin"

// InitializeBridge creates and returns an initialized HueBridge.
// If you have a valid configuration this will be used. Otherwise a local
// discovery will be started, followed by a user registration on your bridge.
func (bridge *HueBridge) InitializeBridge(configuration *Configuration) error {
	err := bridge.discover(configuration.Bridge.IP)
	if err != nil {
		return err
	}
	configuration.Bridge.IP = bridge.BridgeIP

	if configuration.Bridge.Username != "" {
		log.Debugf("⌘ Found bridge username in configuration: %s", configuration.Bridge.Username)
		bridge.Username = configuration.Bridge.Username
	} else {
		log.Debugf("⌘ No username found in bridge configuration. Starting registration...")
		err := bridge.register()
		if err != nil {
			return err
		}
		log.Debugf("⌘ Saving new username in bridge configuration: %s", bridge.Username)
		configuration.Bridge.Username = bridge.Username
	}

	log.Debugf("⌘ Connecting to bridge %s with username %s", bridge.BridgeIP, bridge.Username)
	err = bridge.connect()
	if err != nil {
		return err
	}
	log.Println("⌘ Connection to bridge established")
	go bridge.validateSofwareVersion()
	err = bridge.printDevices()
	if err != nil {
		return err
	}

	err = bridge.populateSchedule(configuration)
	return err
}

// Lights return all known lights on your bridge.
func (bridge *HueBridge) Lights() ([]*Light, error) {
	var lights []*Light
	hueLights, err := bridge.bridge.GetAllLights()
	if err != nil {
		return lights, err
	}

	for _, hueLight := range hueLights {
		var light Light
		light.ID, err = strconv.Atoi(hueLight.Id)
		if err != nil {
			return lights, err
		}

		light.HueLight = *hueLight
		light.initialize()

		lights = append(lights, &light)
	}

	sort.Slice(lights, func(i, j int) bool { return lights[i].ID < lights[j].ID })
	return lights, nil
}

func (bridge *HueBridge) printDevices() error {
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}

	log.Printf("⌘ Devices found on current bridge:")
	log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %-9v | %-8v |", "Name", "ID", "Reachable", "On", "Dimmable", "Temperature", "Color", "Cur. Temp", "Cur. Bri")
	for _, light := range lights {
		var temp string
		if !light.SupportsColorTemperature && !light.SupportsXYColor {
			temp = "-"
		} else {
			temp = strings.Join([]string{strconv.Itoa(light.CurrentLightState.ColorTemperature), "K"}, "")
		}
		log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %9v | %8v |", light.Name, light.ID, light.Reachable, light.On, light.Dimmable, light.SupportsColorTemperature, light.SupportsXYColor, temp, light.CurrentLightState.Brightness)
	}
	return nil
}

func (bridge *HueBridge) discover(ip string) error {
	if ip != "" {
		// we have a known IP address. Validate if it points to a reachable bridge
		bridge.BridgeIP = ip
		err := bridge.validateBridge()
		if err == nil {
			return nil
		}
	}
	log.Debugf("⌘ Starting bridge discovery")
	bridges, err := hue.DiscoverBridges(false)
	if err != nil {
		bridge.BridgeIP = ""
		return err
	}
	if len(bridges) == 0 {
		bridge.BridgeIP = ""
		return errors.New("Bridge discovery failed. Please configure manually in config.json.")
	}
	for _, candidate := range bridges {
		bridge.BridgeIP = candidate.IpAddr
		err := bridge.validateBridge()
		if err == nil {
			log.Printf("⌘ Found bridge at %s", bridge.BridgeIP)
			return nil
		}
	}
	bridge.BridgeIP = ""
	return errors.New("Bridge discovery failed. Please configure manually in config.json.")
}

func (bridge *HueBridge) register() error {
	if bridge.BridgeIP == "" {
		return errors.New("Registration at bridge not possible because no IP is configured. Start discovery first or enter manually.")
	}

	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, "")
	log.Printf("⌘ Starting user registration.")
	log.Warningf("⌘ PLEASE PUSH THE BLUE BUTTON ON YOUR HUE BRIDGE")
	for {
		time.Sleep(5 * time.Second)

		// try user creation, will fail if the button wasn't pressed.
		err := bridge.bridge.CreateUser(hueBridgeAppName)
		if err != nil {
			return err
		}

		if bridge.bridge.Username != "" {
			// registration successful
			bridge.Username = bridge.bridge.Username
			log.Printf("⌘ User registration successful.")
			return nil
		}
	}
}

func (bridge *HueBridge) connect() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge IP configured")
	}

	if bridge.Username == "" {
		return errors.New("No username on bridge configured")
	}
	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, bridge.Username)

	// Test bridge
	_, err := bridge.bridge.Search()
	return err
}

func (bridge *HueBridge) populateSchedule(configuration *Configuration) error {
	if len(configuration.Schedules) == 0 {
		return errors.New("Configuration does not contain any schedules to populate")
	}

	// Do we have associated lights?
	for _, schedule := range configuration.Schedules {
		if len(schedule.AssociatedDeviceIDs) > 0 {
			log.Debugf("⌘ Configuration contains at least one schedule with associated lights.")
			return nil // At least one schedule is configured
		}
	}

	// No schedule has associated lights
	log.Debugf("⌘ Configuration contains no schedule with associated lights. Initializing first schedule with all lights.")
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}
	var lightIDs []int
	for _, light := range lights {
		lightIDs = append(lightIDs, light.ID)
	}
	configuration.Schedules[0].AssociatedDeviceIDs = lightIDs
	return nil
}

func (bridge *HueBridge) validateSofwareVersion() {
	configuration, err := bridge.bridge.Configuration()
	if err != nil {
		log.Warningf("⌘ Could not validate bridge software version: %v", err)
		return
	}

	swversion, err := strconv.Atoi(configuration.SoftwareVersion)
	if err != nil {
		log.Warningf("⌘ Could not validate bridge software version: %v", err)
		return
	}
	log.Debugf("⌘ Bridge is running software version %s", configuration.SoftwareVersion)

	if (bridge.Version == 1 && swversion < 1038802) || (bridge.Version == 2 && swversion < 1709131301) {
		log.Warningf("⌘ Your hue bridge is running an old software version. Please update using the hue app to ensure Kelvin will run smoothly.")
	} else {
		log.Debugf("⌘ Bridge software is up to date")
	}
}

func (bridge *HueBridge) validateBridge() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge configured. Could not validate")
	}
	resp, err := http.Get("http://" + bridge.BridgeIP + "/description.xml")
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	if strings.Contains(string(data), "<modelNumber>929000226503</modelNumber>") {
		bridge.Version = 1
		return nil
	}
	if strings.Contains(string(data), "<modelNumber>BSB002</modelNumber>") {
		bridge.Version = 2
		return nil
	}
	return fmt.Errorf("Bridge validation failed.")
}
<commit_msg>Update latest bridge version<commit_after>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/stefanwichmann/go.hue"
	"io/ioutil"
	"net/http"
	"sort"
	"strconv"
	"strings"
	"time"
)

// HueBridge represents the Philips Hue bridge in
// your system.
// It is used to communicate with all devices.
type HueBridge struct {
	bridge   hue.Bridge
	BridgeIP string
	Username string
	Version  int
}

const hueBridgeAppName = "kelvin"

// InitializeBridge creates and returns an initialized HueBridge.
// If you have a valid configuration this will be used. Otherwise a local
// discovery will be started, followed by a user registration on your bridge.
func (bridge *HueBridge) InitializeBridge(configuration *Configuration) error {
	err := bridge.discover(configuration.Bridge.IP)
	if err != nil {
		return err
	}
	configuration.Bridge.IP = bridge.BridgeIP

	if configuration.Bridge.Username != "" {
		log.Debugf("⌘ Found bridge username in configuration: %s", configuration.Bridge.Username)
		bridge.Username = configuration.Bridge.Username
	} else {
		log.Debugf("⌘ No username found in bridge configuration. Starting registration...")
		err := bridge.register()
		if err != nil {
			return err
		}
		log.Debugf("⌘ Saving new username in bridge configuration: %s", bridge.Username)
		configuration.Bridge.Username = bridge.Username
	}

	log.Debugf("⌘ Connecting to bridge %s with username %s", bridge.BridgeIP, bridge.Username)
	err = bridge.connect()
	if err != nil {
		return err
	}
	log.Println("⌘ Connection to bridge established")
	go bridge.validateSofwareVersion()
	err = bridge.printDevices()
	if err != nil {
		return err
	}

	err = bridge.populateSchedule(configuration)
	return err
}

// Lights return all known lights on your bridge.
func (bridge *HueBridge) Lights() ([]*Light, error) {
	var lights []*Light
	hueLights, err := bridge.bridge.GetAllLights()
	if err != nil {
		return lights, err
	}

	for _, hueLight := range hueLights {
		var light Light
		light.ID, err = strconv.Atoi(hueLight.Id)
		if err != nil {
			return lights, err
		}

		light.HueLight = *hueLight
		light.initialize()

		lights = append(lights, &light)
	}

	sort.Slice(lights, func(i, j int) bool { return lights[i].ID < lights[j].ID })
	return lights, nil
}

func (bridge *HueBridge) printDevices() error {
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}

	log.Printf("⌘ Devices found on current bridge:")
	log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %-9v | %-8v |", "Name", "ID", "Reachable", "On", "Dimmable", "Temperature", "Color", "Cur. Temp", "Cur. Bri")
	for _, light := range lights {
		var temp string
		if !light.SupportsColorTemperature && !light.SupportsXYColor {
			temp = "-"
		} else {
			temp = strings.Join([]string{strconv.Itoa(light.CurrentLightState.ColorTemperature), "K"}, "")
		}
		log.Printf("| %-20s | %3v | %-9v | %-5v | %-8v | %-11v | %-5v | %9v | %8v |", light.Name, light.ID, light.Reachable, light.On, light.Dimmable, light.SupportsColorTemperature, light.SupportsXYColor, temp, light.CurrentLightState.Brightness)
	}
	return nil
}

func (bridge *HueBridge) discover(ip string) error {
	if ip != "" {
		// we have a known IP address. Validate if it points to a reachable bridge
		bridge.BridgeIP = ip
		err := bridge.validateBridge()
		if err == nil {
			return nil
		}
	}
	log.Debugf("⌘ Starting bridge discovery")
	bridges, err := hue.DiscoverBridges(false)
	if err != nil {
		bridge.BridgeIP = ""
		return err
	}
	if len(bridges) == 0 {
		bridge.BridgeIP = ""
		return errors.New("Bridge discovery failed. Please configure manually in config.json.")
	}
	for _, candidate := range bridges {
		bridge.BridgeIP = candidate.IpAddr
		err := bridge.validateBridge()
		if err == nil {
			log.Printf("⌘ Found bridge at %s", bridge.BridgeIP)
			return nil
		}
	}
	bridge.BridgeIP = ""
	return errors.New("Bridge discovery failed. Please configure manually in config.json.")
}

func (bridge *HueBridge) register() error {
	if bridge.BridgeIP == "" {
		return errors.New("Registration at bridge not possible because no IP is configured. Start discovery first or enter manually.")
	}

	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, "")
	log.Printf("⌘ Starting user registration.")
	log.Warningf("⌘ PLEASE PUSH THE BLUE BUTTON ON YOUR HUE BRIDGE")
	for {
		time.Sleep(5 * time.Second)

		// try user creation, will fail if the button wasn't pressed.
		err := bridge.bridge.CreateUser(hueBridgeAppName)
		if err != nil {
			return err
		}

		if bridge.bridge.Username != "" {
			// registration successful
			bridge.Username = bridge.bridge.Username
			log.Printf("⌘ User registration successful.")
			return nil
		}
	}
}

func (bridge *HueBridge) connect() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge IP configured")
	}

	if bridge.Username == "" {
		return errors.New("No username on bridge configured")
	}
	bridge.bridge = *hue.NewBridge(bridge.BridgeIP, bridge.Username)

	// Test bridge
	_, err := bridge.bridge.Search()
	return err
}

func (bridge *HueBridge) populateSchedule(configuration *Configuration) error {
	if len(configuration.Schedules) == 0 {
		return errors.New("Configuration does not contain any schedules to populate")
	}

	// Do we have associated lights?
	for _, schedule := range configuration.Schedules {
		if len(schedule.AssociatedDeviceIDs) > 0 {
			log.Debugf("⌘ Configuration contains at least one schedule with associated lights.")
			return nil // At least one schedule is configured
		}
	}

	// No schedule has associated lights
	log.Debugf("⌘ Configuration contains no schedule with associated lights. Initializing first schedule with all lights.")
	lights, err := bridge.Lights()
	if err != nil {
		return err
	}
	var lightIDs []int
	for _, light := range lights {
		lightIDs = append(lightIDs, light.ID)
	}
	configuration.Schedules[0].AssociatedDeviceIDs = lightIDs
	return nil
}

func (bridge *HueBridge) validateSofwareVersion() {
	configuration, err := bridge.bridge.Configuration()
	if err != nil {
		log.Warningf("⌘ Could not validate bridge software version: %v", err)
		return
	}

	swversion, err := strconv.Atoi(configuration.SoftwareVersion)
	if err != nil {
		log.Warningf("⌘ Could not validate bridge software version: %v", err)
		return
	}
	log.Debugf("⌘ Bridge is running software version %s", configuration.SoftwareVersion)

	if (bridge.Version == 1 && swversion < 1038802) || (bridge.Version == 2 && swversion < 1711151408) {
		log.Warningf("⌘ Your hue bridge is running an old software version. Please update using the hue app to ensure Kelvin will run smoothly.")
	} else {
		log.Debugf("⌘ Bridge software is up to date")
	}
}

func (bridge *HueBridge) validateBridge() error {
	if bridge.BridgeIP == "" {
		return errors.New("No bridge configured. Could not validate")
	}
	resp, err := http.Get("http://" + bridge.BridgeIP + "/description.xml")
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	defer resp.Body.Close()

	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("Could not read bridge description: %v", err)
	}
	if strings.Contains(string(data), "<modelNumber>929000226503</modelNumber>") {
		bridge.Version = 1
		return nil
	}
	if strings.Contains(string(data), "<modelNumber>BSB002</modelNumber>") {
		bridge.Version = 2
		return nil
	}
	return fmt.Errorf("Bridge validation failed.")
}
<|endoftext|>


<commit_before>// package assettube fingerprints and servers your assets processed by
// webpack/gulp/other-tools from your Go application.
//
// AssetTube copys your asset files into a subdirectory named `assettube` and
// fingerprints them, in runtime. Every time the server is restarted, it will
// remove previously generated files and generates new files.
//
package assettube

import (
	"crypto/md5"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"fmt"
	"hash"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

var DefaultManager, _ = NewManager(Config{})

// Add includes path in Manager serving scope. It also copies and fingerprints
// assets into a subdirectory named "assettube". Every time it's called it
// removes the subdirectory and create a new one, then copy all the matched
// files into the new directory.
func Add(root string) error { return DefaultManager.Add(root) }

// ServeHTTP returns the file content based on URL, stripped of URLPrefix.
func ServeHTTP(w http.ResponseWriter, r *http.Request) { DefaultManager.ServeHTTP(w, r) }

// AssetPath returns the fingerprinted filename, with Hostname and URLPrefix if configured.
// It's mostly used as a template function for package html/template or text/template.
func AssetPath(p string) string { return DefaultManager.AssetPath(p) }

// Integrity returns the SRI hash of corresponding file.
// You could specify which digest hash to use by Config.HashType.
func Integrity(p string) string { return DefaultManager.Integrity(p) }

// SetConfig updates Manager configurations.
// Under the hood, it creates a new manager and reprocesses all the monitored directories.
func SetConfig(cfg Config) error { return DefaultManager.SetConfig(cfg) }

// Manager processes and serves the file content.
type Manager struct {
	paths       []string
	pathsMap    map[string]string
	fpPathsMap  map[string]string
	fingerprint bool

	urlPrefix string
	hostname  string

	hash           HashType
	integrity      bool
	integritiesMap map[string]string

	matcher func(path string, info os.FileInfo) bool
}

type Config struct {
	Fingerprint bool   // Fingerprint option. False by default, so you could only enable it for production server.
	URLPrefix   string // URLPrefix prepends url in the fingerprinted filename returned from Manager.
	Hostname    string // Hostname specifies CDN hostname. Empty by default.

	// Matcher decides what files AssetTube Manager should fingerprint and serve.
	// The default matcher only handle JS and CSS files (i.e.: *.js, *.css).
	Matcher func(path string, info os.FileInfo) bool

	// Enable SubresourceIntegrity support and specify digest hash method by HashType.
	SubresourceIntegrity bool
	HashType             HashType // Default HashType is SHA-384.
}

// HashType is the hash
type HashType int

const (
	HTSHA384 HashType = iota
	HTSHA256
	HTSHA512
)

// Hash returns corresponding Hash functions for checksum calculation.
func (h HashType) Hash() hash.Hash {
	switch h {
	case HTSHA256:
		return sha256.New()
	case HTSHA384:
		return sha512.New384()
	case HTSHA512:
		return sha512.New()
	}
	return md5.New()
}

// Strings return HashType's string name.
func (h HashType) String() string {
	switch h {
	case HTSHA256:
		return "sha256"
	case HTSHA384:
		return "sha384"
	case HTSHA512:
		return "sha512"
	}
	return "md5"
}

// NewManager returns a AssetTube Manager.
func NewManager(cfg Config, paths ...string) (*Manager, error) {
	var m Manager
	m.pathsMap = map[string]string{}
	m.fpPathsMap = map[string]string{}
	m.integritiesMap = map[string]string{}
	m.fingerprint = cfg.Fingerprint
	m.urlPrefix = strings.Trim(cfg.URLPrefix, "/")
	m.hostname = cfg.Hostname
	m.integrity = cfg.SubresourceIntegrity
	m.hash = cfg.HashType

	if cfg.Matcher != nil {
		m.matcher = cfg.Matcher
	} else {
		m.matcher = defaultMatcher
	}

	for _, p := range paths {
		if err := m.Add(p); err != nil {
			return nil, err
		}
	}

	return &m, nil
}

func defaultMatcher(path string, info os.FileInfo) bool {
	if matched := strings.HasSuffix(path, ".js"); matched {
		return true
	} else if matched := strings.HasSuffix(path, ".css"); matched {
		return true
	}
	return false
}

// SetConfig updates Manager configurations.
// Under the hood, it creates a new manager and reprocesses all the monitored directories.
func (m *Manager) SetConfig(cfg Config) error {
	nm, err := NewManager(cfg, m.paths...)
	if err != nil {
		return err
	}
	*m = *nm
	return nil
}

// Add includes path in Manager serving scope. It also copies and fingerprints
// assets into a subdirectory named "assettube". Every time it's called it
// removes the subdirectory and create a new one, then copy all the matched
// files into the new directory.
func (m *Manager) Add(root string) error {
	m.paths = append(m.paths, root)

	root = filepath.Clean(root)
	cacheDir := root
	if m.fingerprint {
		cacheDir = filepath.Join(root, "assettube")
		if _, err := os.Stat(cacheDir); err != nil {
			if !os.IsNotExist(err) {
				return err
			}
		} else if err := os.RemoveAll(cacheDir); err != nil {
			return err
		}
		if err := os.Mkdir(cacheDir, 0755); err != nil {
			return err
		}
	}

	if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		name := strings.TrimPrefix(strings.TrimPrefix(path, root), string(filepath.Separator))
		if info.IsDir() {
			if name == "" {
				return nil
			} else if name == "assettube" {
				return filepath.SkipDir
			}

			if !m.fingerprint {
				return nil
			}

			if err := os.Mkdir(filepath.Join(cacheDir, name), info.Mode()); err != nil {
				return err
			}
			return nil
		}

		src, err := os.Open(path)
		if err != nil {
			return err
		}
		defer src.Close()

		// skip fingerprinting for dev mode
		if !m.fingerprint {
			m.pathsMap[name] = name
			m.fpPathsMap[name] = filepath.Join(cacheDir, name)
			return nil
		}

		if !m.matcher(path, info) {
			return nil
		}

		// generate fingerprinted filename
		hash := md5.New()
		if _, err := io.Copy(hash, src); err != nil {
			return err
		}
		if _, err := src.Seek(0, 0); err != nil {
			return err
		}
		ext := filepath.Ext(path)
		fpname := fmt.Sprintf("%s.%x%s", strings.TrimSuffix(name, ext), hash.Sum(nil), ext)
		m.pathsMap[name] = fpname
		m.fpPathsMap[fpname] = filepath.Join(cacheDir, fpname)
		if m.integrity {
			hash := m.hash.Hash()
			if _, err := io.Copy(hash, src); err != nil {
				return err
			}
			if _, err := src.Seek(0, 0); err != nil {
				return err
			}
			m.integritiesMap[name] = base64.RawStdEncoding.EncodeToString(hash.Sum(nil))
		}

		// copy file to assettube/
		dst, err := os.OpenFile(m.fpPathsMap[m.pathsMap[name]], os.O_WRONLY|os.O_TRUNC|os.O_CREATE, info.Mode())
		if err != nil {
			return err
		}
		if _, err := io.Copy(dst, src); err != nil {
			return err
		}
		dst.Close()

		return nil
	}); err != nil {
		return err
	}

	return nil
}

// ServeHTTP returns the file content based on URL, stripped of URLPrefix.
func (m *Manager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if m.urlPrefix != "" {
		path = strings.TrimPrefix(path, "/"+m.urlPrefix)
	}
	path = strings.TrimPrefix(path, "/")
	http.ServeFile(w, r, m.fpPathsMap[path])
}

// AssetPath returns the fingerprinted filename, with Hostname and URLPrefix if configured.
// It's mostly used as a template function for package html/template or text/template.
func (m *Manager) AssetPath(p string) string {
	paths := make([]string, 0, 3)
	if m.hostname != "" {
		paths = append(paths, m.hostname)
	}
	if m.urlPrefix != "" {
		paths = append(paths, m.urlPrefix)
	}
	paths = append(paths, m.pathsMap[p])

	if m.hostname != "" {
		return strings.Join(paths, "/")
	}
	return "/" + strings.Join(paths, "/")
}

// Integrity returns the SRI hash of corresponding file.
// You could specify which digest hash to use by Config.HashType.
func (m *Manager) Integrity(p string) string {
	if m.integritiesMap[p] == "" {
		return ""
	}
	return fmt.Sprintf("%s-%s", m.hash, m.integritiesMap[p])
}
<commit_msg>Update docs<commit_after>// package assettube fingerprints and servers your assets processed by
// webpack/gulp/other-tools from your Go application.
//
// AssetTube copys your asset files into a subdirectory named `assettube` and
// fingerprints them, in runtime. Every time the server is restarted, it will
// remove previously generated files and generates new files.
//
package assettube

import (
	"crypto/md5"
	"crypto/sha256"
	"crypto/sha512"
	"encoding/base64"
	"fmt"
	"hash"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
)

var DefaultManager, _ = NewManager(Config{})

// Add includes path in Manager serving scope. It also copies and fingerprints
// assets into a subdirectory named "assettube". Every time it's called it
// removes the subdirectory and create a new one, then copy all the matched
// files into the new directory.
func Add(root string) error { return DefaultManager.Add(root) }

// ServeHTTP returns the file content based on URL, stripped of URLPrefix.
func ServeHTTP(w http.ResponseWriter, r *http.Request) { DefaultManager.ServeHTTP(w, r) }

// AssetPath returns the fingerprinted filename, with Hostname and URLPrefix if configured.
// It's mostly used as a template function for package html/template or text/template.
func AssetPath(p string) string { return DefaultManager.AssetPath(p) }

// Integrity returns the SRI hash of corresponding file.
// You could specify which digest hash to use by Config.HashType.
// https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
func Integrity(p string) string { return DefaultManager.Integrity(p) }

// SetConfig updates Manager configurations.
// Under the hood, it creates a new manager and reprocesses all the monitored directories.
func SetConfig(cfg Config) error { return DefaultManager.SetConfig(cfg) }

// Manager processes and serves the file content.
type Manager struct {
	paths       []string
	pathsMap    map[string]string
	fpPathsMap  map[string]string
	fingerprint bool

	urlPrefix string
	hostname  string

	hash           HashType
	integrity      bool
	integritiesMap map[string]string

	matcher func(path string, info os.FileInfo) bool
}

type Config struct {
	Fingerprint bool   // Fingerprint option. False by default, so you could only enable it for production server.
	URLPrefix   string // URLPrefix prepends url in the fingerprinted filename returned from Manager.
	Hostname    string // Hostname specifies CDN hostname. Empty by default.

	// Matcher decides what files AssetTube Manager should fingerprint and serve.
	// The default matcher only handle JS and CSS files (i.e.: *.js, *.css).
	Matcher func(path string, info os.FileInfo) bool

	// Enable SubresourceIntegrity support and specify digest hash method by HashType.
	SubresourceIntegrity bool
	HashType             HashType // Default HashType is SHA-384.
}

// HashType is the hash
type HashType int

const (
	HTSHA384 HashType = iota
	HTSHA256
	HTSHA512
)

// Hash returns corresponding Hash functions for checksum calculation.
func (h HashType) Hash() hash.Hash {
	switch h {
	case HTSHA256:
		return sha256.New()
	case HTSHA384:
		return sha512.New384()
	case HTSHA512:
		return sha512.New()
	}
	return md5.New()
}

// Strings return HashType's string name.
func (h HashType) String() string {
	switch h {
	case HTSHA256:
		return "sha256"
	case HTSHA384:
		return "sha384"
	case HTSHA512:
		return "sha512"
	}
	return "md5"
}

// NewManager returns a AssetTube Manager.
func NewManager(cfg Config, paths ...string) (*Manager, error) {
	var m Manager
	m.pathsMap = map[string]string{}
	m.fpPathsMap = map[string]string{}
	m.integritiesMap = map[string]string{}
	m.fingerprint = cfg.Fingerprint
	m.urlPrefix = strings.Trim(cfg.URLPrefix, "/")
	m.hostname = cfg.Hostname
	m.integrity = cfg.SubresourceIntegrity
	m.hash = cfg.HashType

	if cfg.Matcher != nil {
		m.matcher = cfg.Matcher
	} else {
		m.matcher = defaultMatcher
	}

	for _, p := range paths {
		if err := m.Add(p); err != nil {
			return nil, err
		}
	}

	return &m, nil
}

func defaultMatcher(path string, info os.FileInfo) bool {
	if matched := strings.HasSuffix(path, ".js"); matched {
		return true
	} else if matched := strings.HasSuffix(path, ".css"); matched {
		return true
	}
	return false
}

// SetConfig updates Manager configurations.
// Under the hood, it creates a new manager and reprocesses all the monitored directories.
func (m *Manager) SetConfig(cfg Config) error {
	nm, err := NewManager(cfg, m.paths...)
	if err != nil {
		return err
	}
	*m = *nm
	return nil
}

// Add includes path in Manager serving scope. It also copies and fingerprints
// assets into a subdirectory named "assettube". Every time it's called it
// removes the subdirectory and create a new one, then copy all the matched
// files into the new directory.
func (m *Manager) Add(root string) error {
	m.paths = append(m.paths, root)

	root = filepath.Clean(root)
	cacheDir := root
	if m.fingerprint {
		cacheDir = filepath.Join(root, "assettube")
		if _, err := os.Stat(cacheDir); err != nil {
			if !os.IsNotExist(err) {
				return err
			}
		} else if err := os.RemoveAll(cacheDir); err != nil {
			return err
		}
		if err := os.Mkdir(cacheDir, 0755); err != nil {
			return err
		}
	}

	if err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		name := strings.TrimPrefix(strings.TrimPrefix(path, root), string(filepath.Separator))
		if info.IsDir() {
			if name == "" {
				return nil
			} else if name == "assettube" {
				return filepath.SkipDir
			}

			if !m.fingerprint {
				return nil
			}

			if err := os.Mkdir(filepath.Join(cacheDir, name), info.Mode()); err != nil {
				return err
			}
			return nil
		}

		src, err := os.Open(path)
		if err != nil {
			return err
		}
		defer src.Close()

		// skip fingerprinting for dev mode
		if !m.fingerprint {
			m.pathsMap[name] = name
			m.fpPathsMap[name] = filepath.Join(cacheDir, name)
			return nil
		}

		if !m.matcher(path, info) {
			return nil
		}

		// generate fingerprinted filename
		hash := md5.New()
		if _, err := io.Copy(hash, src); err != nil {
			return err
		}
		if _, err := src.Seek(0, 0); err != nil {
			return err
		}
		ext := filepath.Ext(path)
		fpname := fmt.Sprintf("%s.%x%s", strings.TrimSuffix(name, ext), hash.Sum(nil), ext)
		m.pathsMap[name] = fpname
		m.fpPathsMap[fpname] = filepath.Join(cacheDir, fpname)
		if m.integrity {
			hash := m.hash.Hash()
			if _, err := io.Copy(hash, src); err != nil {
				return err
			}
			if _, err := src.Seek(0, 0); err != nil {
				return err
			}
			m.integritiesMap[name] = base64.RawStdEncoding.EncodeToString(hash.Sum(nil))
		}

		// copy file to assettube/
		dst, err := os.OpenFile(m.fpPathsMap[m.pathsMap[name]], os.O_WRONLY|os.O_TRUNC|os.O_CREATE, info.Mode())
		if err != nil {
			return err
		}
		if _, err := io.Copy(dst, src); err != nil {
			return err
		}
		dst.Close()

		return nil
	}); err != nil {
		return err
	}

	return nil
}

// ServeHTTP returns the file content based on URL, stripped of URLPrefix.
func (m *Manager) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if m.urlPrefix != "" {
		path = strings.TrimPrefix(path, "/"+m.urlPrefix)
	}
	path = strings.TrimPrefix(path, "/")
	http.ServeFile(w, r, m.fpPathsMap[path])
}

// AssetPath returns the fingerprinted filename, with Hostname and URLPrefix if configured.
// It's mostly used as a template function for package html/template or text/template.
func (m *Manager) AssetPath(p string) string {
	paths := make([]string, 0, 3)
	if m.hostname != "" {
		paths = append(paths, m.hostname)
	}
	if m.urlPrefix != "" {
		paths = append(paths, m.urlPrefix)
	}
	paths = append(paths, m.pathsMap[p])

	if m.hostname != "" {
		return strings.Join(paths, "/")
	}
	return "/" + strings.Join(paths, "/")
}

// Integrity returns the SRI hash of corresponding file.
// You could specify which digest hash to use by Config.HashType.
// https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
func (m *Manager) Integrity(p string) string {
	if m.integritiesMap[p] == "" {
		return ""
	}
	return fmt.Sprintf("%s-%s", m.hash, m.integritiesMap[p])
}
<|endoftext|>


<commit_before>package algoliaconnector

import (
	"math/rand"
	"socialapi/models"
	"socialapi/workers/common/runner"
	"strconv"

	"github.com/algolia/algoliasearch-client-go/algoliasearch"
	"github.com/koding/bongo"
	"labix.org/v2/mgo/bson"

	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
)

const algoliaSlownessSeconds = 2 * time.Second

func TestTopicSaved(t *testing.T) {
	r := runner.New("AlogoliaConnector-Test")
	err := r.Init()
	if err != nil {
		panic(err)
	}

	defer r.Close()

	algolia := algoliasearch.NewClient(r.Conf.Algolia.AppId, r.Conf.Algolia.ApiSecretKey)
	// create message handler
	handler := New(r.Log, algolia, r.Conf.Algolia.IndexSuffix)

	Convey("given some fake topic channel", t, func() {
		mockTopic := models.NewChannel()
		mockTopic.TypeConstant = models.Channel_TYPE_TOPIC
		Convey("it should save the document to algolia", func() {
			err := handler.TopicSaved(mockTopic)
			So(err, ShouldBeNil)
		})
	})
	Convey("given some fake non-topic channel", t, func() {
		mockTopic := models.NewChannel()
		mockTopic.TypeConstant = models.Channel_TYPE_PRIVATE_MESSAGE
		Convey("it should save the document to algolia", func() {
			err := handler.TopicSaved(mockTopic)
			So(err, ShouldBeNil)
		})
	})

}

func TestAccountSaved(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	Convey("given some fake account", t, func() {
		mockAccount := &models.Account{
			OldId:   bson.NewObjectId().Hex(),
			Id:      100000000,
			Nick:    "fake-nickname",
			IsTroll: false,
		}
		Convey("it should save the document to algolia", func() {
			err := handler.AccountSaved(mockAccount)
			So(err, ShouldBeNil)
		})
	})
}

func TestMessageListSaved(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	Convey("messages can be saved", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(record, ShouldNotBeNil)
	})
	Convey("messages can be cross-indexed", t, func() {
		mockMessage, owner := createAndSaveMessage()

		// init channel
		cm, err := createChannel(owner.Id)
		So(err, ShouldBeNil)
		So(cm, ShouldNotBeNil)
		// init channel message list
		cml := createChannelMessageList(cm.Id, mockMessage.Id)
		So(cml, ShouldNotBeNil)

		listings := getListings(mockMessage)
		So(len(listings), ShouldEqual, 2)

		So(handler.MessageListSaved(&listings[0]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)
		So(handler.MessageListSaved(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 2)
	})
}

func TestMessageListDeleted(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	Convey("messages can be deleted", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		So(handler.MessageListDeleted(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err.Error(), ShouldEqual, ErrAlgoliaObjectIdNotFound.Error())
		So(record, ShouldBeNil)
	})
	Convey("cross-indexed messages will not be deleted", t, func() {
		mockMessage, owner := createAndSaveMessage()

		// init channel
		cm, err := createChannel(owner.Id)
		So(err, ShouldBeNil)
		So(cm, ShouldNotBeNil)
		// init channel message list
		cml := createChannelMessageList(cm.Id, mockMessage.Id)
		So(cml, ShouldNotBeNil)

		listings := getListings(mockMessage)
		So(len(listings), ShouldEqual, 2)

		So(handler.MessageListSaved(&listings[0]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)
		So(handler.MessageListSaved(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 2)

		So(handler.MessageListDeleted(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err = handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 1)
	})
}

func TestMessageUpdated(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	Convey("messages can be updated", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		mockMessage.Body = "updated body"

		So(mockMessage.Update(), ShouldBeNil)
		So(handler.MessageUpdated(mockMessage), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(record["body"].(string), ShouldEqual, "updated body")
	})
}

func getTestHandler() (*runner.Runner, *Controller) {
	r := runner.New("AlogoliaConnector-Test")
	err := r.Init()
	if err != nil {
		panic(err)
	}
	algolia := algoliasearch.NewClient(r.Conf.Algolia.AppId, r.Conf.Algolia.ApiSecretKey)
	// create message handler
	return r, New(r.Log, algolia, ".test")

}

func createAccount() (*models.Account, error) {
	// create and account instance
	author := models.NewAccount()

	// create a fake mongo id
	oldId := bson.NewObjectId()
	// assign it to our test user
	author.OldId = oldId.Hex()

	// seed the random data generator
	rand.Seed(time.Now().UnixNano())

	author.Nick = "malitest" + strconv.Itoa(rand.Intn(10e9))

	if err := author.Create(); err != nil {
		return nil, err
	}

	return author, nil
}

func createChannel(accountId int64) (*models.Channel, error) {
	// create and account instance
	channel := models.NewChannel()
	channel.CreatorId = accountId

	if err := channel.Create(); err != nil {
		return nil, err
	}

	return channel, nil
}

func createChannelMessageList(channelId, messageId int64) *models.ChannelMessageList {
	cml := models.NewChannelMessageList()

	cml.ChannelId = channelId
	cml.MessageId = messageId

	So(cml.Create(), ShouldBeNil)

	return cml
}

func createAndSaveMessage() (*models.ChannelMessage, *models.Account) {
	cm := models.NewChannelMessage()

	// init account
	account, err := createAccount()
	So(err, ShouldBeNil)
	So(account, ShouldNotBeNil)
	So(account.Id, ShouldNotEqual, 0)
	// init channel
	channel, err := createChannel(account.Id)
	So(err, ShouldBeNil)
	So(channel, ShouldNotBeNil)
	// set account id
	cm.AccountId = account.Id
	// set channel id
	cm.InitialChannelId = channel.Id
	// set body
	cm.Body = "5five"
	So(cm.Create(), ShouldBeNil)
	// init listing
	cml := createChannelMessageList(channel.Id, cm.Id)
	So(cml, ShouldNotBeNil)

	return cm, account
}

func getListings(message *models.ChannelMessage) []models.ChannelMessageList {
	mockListing := models.NewChannelMessageList()
	var listings []models.ChannelMessageList
	err := mockListing.Some(&listings, &bongo.Query{
		Selector: map[string]interface{}{"message_id": message.Id}})
	So(err, ShouldBeNil)
	return listings
}
<commit_msg>algoliaconnector: temporarily disable algolia tests<commit_after>package algoliaconnector

import (
	"math/rand"
	"socialapi/models"
	"socialapi/workers/common/runner"
	"strconv"

	"github.com/algolia/algoliasearch-client-go/algoliasearch"
	"github.com/koding/bongo"
	"labix.org/v2/mgo/bson"

	"testing"
	"time"

	. "github.com/smartystreets/goconvey/convey"
)

const algoliaSlownessSeconds = 2 * time.Second

func TestTopicSaved(t *testing.T) {
	r := runner.New("AlogoliaConnector-Test")
	err := r.Init()
	if err != nil {
		panic(err)
	}

	defer r.Close()

	algolia := algoliasearch.NewClient(r.Conf.Algolia.AppId, r.Conf.Algolia.ApiSecretKey)
	// create message handler
	handler := New(r.Log, algolia, r.Conf.Algolia.IndexSuffix)

	Convey("given some fake topic channel", t, func() {
		mockTopic := models.NewChannel()
		mockTopic.TypeConstant = models.Channel_TYPE_TOPIC
		Convey("it should save the document to algolia", func() {
			err := handler.TopicSaved(mockTopic)
			So(err, ShouldBeNil)
		})
	})
	Convey("given some fake non-topic channel", t, func() {
		mockTopic := models.NewChannel()
		mockTopic.TypeConstant = models.Channel_TYPE_PRIVATE_MESSAGE
		Convey("it should save the document to algolia", func() {
			err := handler.TopicSaved(mockTopic)
			So(err, ShouldBeNil)
		})
	})

}

func TestAccountSaved(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	Convey("given some fake account", t, func() {
		mockAccount := &models.Account{
			OldId:   bson.NewObjectId().Hex(),
			Id:      100000000,
			Nick:    "fake-nickname",
			IsTroll: false,
		}
		Convey("it should save the document to algolia", func() {
			err := handler.AccountSaved(mockAccount)
			So(err, ShouldBeNil)
		})
	})
}

func TestMessageListSaved(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	SkipConvey("messages can be saved", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(record, ShouldNotBeNil)
	})
	SkipConvey("messages can be cross-indexed", t, func() {
		mockMessage, owner := createAndSaveMessage()

		// init channel
		cm, err := createChannel(owner.Id)
		So(err, ShouldBeNil)
		So(cm, ShouldNotBeNil)
		// init channel message list
		cml := createChannelMessageList(cm.Id, mockMessage.Id)
		So(cml, ShouldNotBeNil)

		listings := getListings(mockMessage)
		So(len(listings), ShouldEqual, 2)

		So(handler.MessageListSaved(&listings[0]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)
		So(handler.MessageListSaved(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 2)
	})
}

func TestMessageListDeleted(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	SkipConvey("messages can be deleted", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		So(handler.MessageListDeleted(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err.Error(), ShouldEqual, ErrAlgoliaObjectIdNotFound.Error())
		So(record, ShouldBeNil)
	})
	SkipConvey("cross-indexed messages will not be deleted", t, func() {
		mockMessage, owner := createAndSaveMessage()

		// init channel
		cm, err := createChannel(owner.Id)
		So(err, ShouldBeNil)
		So(cm, ShouldNotBeNil)
		// init channel message list
		cml := createChannelMessageList(cm.Id, mockMessage.Id)
		So(cml, ShouldNotBeNil)

		listings := getListings(mockMessage)
		So(len(listings), ShouldEqual, 2)

		So(handler.MessageListSaved(&listings[0]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)
		So(handler.MessageListSaved(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 2)

		So(handler.MessageListDeleted(&listings[1]), ShouldBeNil)
		time.Sleep(algoliaSlownessSeconds)

		record, err = handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(len((record["_tags"]).([]interface{})), ShouldEqual, 1)
	})
}

func TestMessageUpdated(t *testing.T) {
	runner, handler := getTestHandler()
	defer runner.Close()

	SkipConvey("messages can be updated", t, func() {
		mockMessage, _ := createAndSaveMessage()
		mockListing := getListings(mockMessage)[0]

		So(handler.MessageListSaved(&mockListing), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		mockMessage.Body = "updated body"

		So(mockMessage.Update(), ShouldBeNil)
		So(handler.MessageUpdated(mockMessage), ShouldBeNil)

		time.Sleep(algoliaSlownessSeconds)

		record, err := handler.get("messages", strconv.FormatInt(mockMessage.Id, 10))
		So(err, ShouldBeNil)
		So(record["body"].(string), ShouldEqual, "updated body")
	})
}

func getTestHandler() (*runner.Runner, *Controller) {
	r := runner.New("AlogoliaConnector-Test")
	err := r.Init()
	if err != nil {
		panic(err)
	}
	algolia := algoliasearch.NewClient(r.Conf.Algolia.AppId, r.Conf.Algolia.ApiSecretKey)
	// create message handler
	return r, New(r.Log, algolia, ".test")

}

func createAccount() (*models.Account, error) {
	// create and account instance
	author := models.NewAccount()

	// create a fake mongo id
	oldId := bson.NewObjectId()
	// assign it to our test user
	author.OldId = oldId.Hex()

	// seed the random data generator
	rand.Seed(time.Now().UnixNano())

	author.Nick = "malitest" + strconv.Itoa(rand.Intn(10e9))

	if err := author.Create(); err != nil {
		return nil, err
	}

	return author, nil
}

func createChannel(accountId int64) (*models.Channel, error) {
	// create and account instance
	channel := models.NewChannel()
	channel.CreatorId = accountId

	if err := channel.Create(); err != nil {
		return nil, err
	}

	return channel, nil
}

func createChannelMessageList(channelId, messageId int64) *models.ChannelMessageList {
	cml := models.NewChannelMessageList()

	cml.ChannelId = channelId
	cml.MessageId = messageId

	So(cml.Create(), ShouldBeNil)

	return cml
}

func createAndSaveMessage() (*models.ChannelMessage, *models.Account) {
	cm := models.NewChannelMessage()

	// init account
	account, err := createAccount()
	So(err, ShouldBeNil)
	So(account, ShouldNotBeNil)
	So(account.Id, ShouldNotEqual, 0)
	// init channel
	channel, err := createChannel(account.Id)
	So(err, ShouldBeNil)
	So(channel, ShouldNotBeNil)
	// set account id
	cm.AccountId = account.Id
	// set channel id
	cm.InitialChannelId = channel.Id
	// set body
	cm.Body = "5five"
	So(cm.Create(), ShouldBeNil)
	// init listing
	cml := createChannelMessageList(channel.Id, cm.Id)
	So(cml, ShouldNotBeNil)

	return cm, account
}

func getListings(message *models.ChannelMessage) []models.ChannelMessageList {
	mockListing := models.NewChannelMessageList()
	var listings []models.ChannelMessageList
	err := mockListing.Some(&listings, &bongo.Query{
		Selector: map[string]interface{}{"message_id": message.Id}})
	So(err, ShouldBeNil)
	return listings
}
<|endoftext|>


<commit_before>// Copyright 2016 Koichi Shiraishi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package terminal

import (
	"fmt"
	"strings"
	"sync"

	"nvim-go/config"
	"nvim-go/nvim"
	"nvim-go/pathutil"

	"github.com/garyburd/neovim-go/vim"
)

var pkgTerminal = "GoTerminal"

var bufName = "__GO_TERMINAL__"

// Terminal configure of open the terminal.
type Terminal struct {
	v    *vim.Vim
	cmd  []string
	mode string
	Name string
	// Dir specifies the working directory of the command on terminal.
	Dir string
	// Width split window width for open the terminal window.
	Width int64
	// Height split window height for open the terminal window.
	Height int64
	Size   int

	cb     vim.Buffer
	cw     vim.Window
	Buffer *nvim.Buffer
}

// NewTerminal return the initialize Neovim terminal config.
func NewTerminal(vim *vim.Vim, name string, command []string, mode string) *Terminal {
	return &Terminal{
		v:    vim,
		Name: name,
		cmd:  command,
		mode: mode,
	}
}

// Create creats the new Neovim terminal buffer.
func (t *Terminal) Create() error {
	p := t.v.NewPipeline()
	p.CurrentBuffer(&t.cb)
	p.CurrentWindow(&t.cw)
	if err := p.Wait(); err != nil {
		return err
	}

	t.Height = config.TerminalHeight
	t.Width = config.TerminalWidth

	switch {
	case t.Height != int64(0) && t.mode == "split":
		t.Size = int(t.Height)
	case t.Width != int64(0) && t.mode == "vsplit":
		t.Size = int(t.Width)
	case strings.Index(t.mode, "split") == -1:
		return fmt.Errorf("%s mode is not supported", t.mode)
	}

	option := t.setTerminalOption()
	name := fmt.Sprintf("| terminal %s", strings.Join(t.cmd, " "))
	mode := fmt.Sprintf("%s %d%s", config.TerminalPosition, t.Size, t.mode)
	t.Buffer = nvim.NewBuffer(t.v, name, nvim.FiletypeTerminal, mode, option)
	t.Buffer.Name = t.Name
	t.Buffer.UpdateSyntax("")

	// Get terminal buffer and windows information.
	p.CurrentBuffer(&t.Buffer.Buffer)
	p.CurrentWindow(&t.Buffer.Window)
	if err := p.Wait(); err != nil {
		return err
	}

	// Cleanup cursor highlighting
	// TODO(zchee): Can use p.ClearBufferHighlight?
	p.Command("highlight TermCursor gui=NONE guifg=NONE guibg=NONE")
	p.Command("highlight TermCursorNC gui=NONE guifg=NONE guibg=NONE")

	// Cleanup autocmd for terminal buffer
	// The following autocmd is defined only in the terminal buffer local
	p.Command("autocmd! * <buffer>")
	// Set autoclose buffer if the current buffer is only terminal
	// TODO(zchee): convert to rpc way
	p.Command("autocmd WinEnter <buffer> if winnr('$') == 1 | quit | endif")

	return p.Wait()
}

// Run runs the command in the terminal buffer.
func (t *Terminal) Run(cmd []string) error {
	if t.Dir != "" {
		defer pathutil.Chdir(t.v, t.Dir)()
	}

	if t.Buffer != nil && nvim.BufContains(t.v, t.Buffer.Buffer) {
		defer t.switchFocus()()

		t.v.SetBufferOption(t.Buffer.Buffer, nvim.BufOptionModified, false)
		t.v.Call("termopen", nil, cmd)
	} else {
		t.Create()
		defer t.switchFocus()()
	}
	// Workaround for "autocmd BufEnter term://* startinsert"
	if config.TerminalStartInsert {
		t.v.Command("stopinsert")
	}

	return nil
}

func (t *Terminal) setTerminalOption() map[nvim.VimOption]map[string]interface{} {
	option := make(map[nvim.VimOption]map[string]interface{})
	bufoption := make(map[string]interface{})
	bufvar := make(map[string]interface{})
	windowoption := make(map[string]interface{})

	bufoption[nvim.BufOptionBufhidden] = nvim.BufhiddenDelete
	bufoption[nvim.BufOptionBuflisted] = false
	bufoption[nvim.BufOptionBuftype] = nvim.BuftypeNofile
	bufoption[nvim.BufOptionFiletype] = nvim.FiletypeTerminal
	bufoption[nvim.BufOptionModifiable] = false
	bufoption[nvim.BufOptionSwapfile] = false

	bufvar[nvim.BufVarColorcolumn] = ""

	windowoption[nvim.WinOptionList] = false
	windowoption[nvim.WinOptionNumber] = false
	windowoption[nvim.WinOptionRelativenumber] = false
	windowoption[nvim.WinOptionWinfixheight] = true

	option[nvim.BufferOption] = bufoption
	option[nvim.BufferVar] = bufvar
	option[nvim.WindowOption] = windowoption

	return option
}

// TODO(zchee): flashing when switch the window.
func (t *Terminal) switchFocus() func() {
	t.v.SetCurrentWindow(t.Buffer.Window)

	return func() {
		t.v.SetCurrentWindow(t.cw)
	}
}

// chdir changes vim current working directory.
// The returned function restores working directory to `getcwd()` result path
// and unlocks the mutex.
func chdir(v *vim.Vim, dir string) func() {
	var (
		m   sync.Mutex
		cwd interface{}
	)
	m.Lock()
	if err := v.Eval("getcwd()", &cwd); err != nil {
		nvim.Echoerr(v, "GoTerminal: %v", err)
	}
	v.ChangeDirectory(dir)
	return func() {
		v.ChangeDirectory(cwd.(string))
		m.Unlock()
	}
}
<commit_msg>terminal: Add Terminal.Pipeline and Fix small things<commit_after>// Copyright 2016 Koichi Shiraishi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package terminal

import (
	"fmt"
	"strings"
	"sync"

	"nvim-go/config"
	"nvim-go/nvim"
	"nvim-go/pathutil"

	"github.com/garyburd/neovim-go/vim"
	"github.com/juju/errors"
)

var pkgTerminal = "GoTerminal"

var bufName = "__GO_TERMINAL__"

// Terminal represents a Neovim terminal.
type Terminal struct {
	v *vim.Vim
	p *vim.Pipeline

	cmd  []string
	mode string
	// Name terminal buffer name.
	Name string
	// Dir specifies the working directory of the command on terminal.
	Dir string
	// Size open the terminal window size.
	Size int

	cw vim.Window

	*nvim.Buf
}

// NewTerminal return the Neovim terminal buffer.
func NewTerminal(vim *vim.Vim, name string, command []string, mode string) *Terminal {
	return &Terminal{
		v:    vim,
		p:    vim.NewPipeline(),
		Name: name,
		cmd:  command,
		mode: mode,
	}
}

// Create creats the new Neovim terminal buffer.
func (t *Terminal) Create() (err error) {
	t.cw, err = t.v.CurrentWindow()
	if err != nil {
		return err
	}

	switch {
	case t.mode == "split":
		t.Size = int(config.TerminalHeight)
	case t.mode == "vsplit":
		t.Size = int(config.TerminalWidth)
	default:
		err := errors.Errorf("%s mode is not supported", t.mode)
		return nvim.ErrorWrap(t.v, errors.Annotate(err, pkgTerminal))
	}

	option := t.setTerminalOption()
	name := fmt.Sprintf("| terminal %s", strings.Join(t.cmd, " "))
	mode := fmt.Sprintf("%s %d%s", config.TerminalPosition, t.Size, t.mode)
	t.Buf = nvim.NewBuffer(t.v, name, nvim.FiletypeTerminal, mode, option)
	t.Buf.Name = t.Name
	t.Buf.UpdateSyntax(nvim.FiletypeTerminal)

	// Get terminal buffer and windows information.
	t.p.CurrentBuffer(&t.Buffer)
	t.p.CurrentWindow(&t.Window)
	if err := t.p.Wait(); err != nil {
		return err
	}
	defer t.switchFocus()()

	// Cleanup cursor highlighting
	// TODO(zchee): Can use p.ClearBufferHighlight?
	t.p.Command("highlight TermCursor gui=NONE guifg=NONE guibg=NONE")
	t.p.Command("highlight TermCursorNC gui=NONE guifg=NONE guibg=NONE")

	// Set autoclose buffer if the current buffer is only terminal
	// TODO(zchee): convert to rpc way
	t.p.Command("autocmd WinEnter <buffer> if winnr('$') == 1 | quit | endif")

	return t.p.Wait()
}

// Run runs the command in the terminal buffer.
func (t *Terminal) Run(cmd []string) error {
	if t.Dir != "" {
		defer pathutil.Chdir(t.v, t.Dir)()
	}

	if t.Buf != nil && nvim.IsBufferValid(t.v, t.Buffer) {
		defer t.switchFocus()()

		t.v.SetBufferOption(t.Buffer, nvim.BufOptionModified, false)
		t.v.Call("termopen", nil, cmd)
		t.v.SetBufferName(t.Buffer, t.Buf.Name)
	} else {
		t.Create()
	}
	// Workaround for "autocmd BufEnter term://* startinsert"
	if config.TerminalStartInsert {
		t.v.Command("stopinsert")
	}

	return nil
}

// TODO(zchee): flashing when switch the window.
func (t *Terminal) switchFocus() func() {
	t.v.SetCurrentWindow(t.Window)

	return func() {
		t.v.SetCurrentWindow(t.cw)
	}
}

// chdir changes vim current working directory.
// The returned function restores working directory to `getcwd()` result path
// and unlocks the mutex.
func chdir(v *vim.Vim, dir string) func() {
	var (
		m   sync.Mutex
		cwd interface{}
	)
	m.Lock()
	if err := v.Eval("getcwd()", &cwd); err != nil {
		nvim.Echoerr(v, "GoTerminal: %v", err)
	}
	v.ChangeDirectory(dir)
	return func() {
		v.ChangeDirectory(cwd.(string))
		m.Unlock()
	}
}

func (t *Terminal) setTerminalOption() map[nvim.NvimOption]map[string]interface{} {
	option := make(map[nvim.NvimOption]map[string]interface{})
	bufoption := make(map[string]interface{})
	bufvar := make(map[string]interface{})
	windowoption := make(map[string]interface{})

	bufoption[nvim.BufOptionBufhidden] = nvim.BufhiddenDelete
	bufoption[nvim.BufOptionBuflisted] = false
	bufoption[nvim.BufOptionBuftype] = nvim.BuftypeNofile
	bufoption[nvim.BufOptionFiletype] = nvim.FiletypeTerminal
	bufoption[nvim.BufOptionModifiable] = false
	bufoption[nvim.BufOptionSwapfile] = false

	bufvar[nvim.BufVarColorcolumn] = ""

	windowoption[nvim.WinOptionList] = false
	windowoption[nvim.WinOptionNumber] = false
	windowoption[nvim.WinOptionRelativenumber] = false
	windowoption[nvim.WinOptionWinfixheight] = true

	option[nvim.BufferOption] = bufoption
	option[nvim.BufferVar] = bufvar
	option[nvim.WindowOption] = windowoption

	return option
}
<|endoftext|>