first commit
way too late.
This commit is contained in:
commit
d4f7dc57fc
53 files changed
+3518
No files matched your search
@@ -0,0 +1,118 @@
|
||||
package doormen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/didiercrunch/doorman/shared"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
const epsilon float64 = 0.0001
|
||||
|
||||
type DoormanValue struct {
|
||||
Name string `json:"name" bson:"name"`
|
||||
Probability float64 `json:"probability" bson:"probability"`
|
||||
}
|
||||
|
||||
type DoormanId struct {
|
||||
Id bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
}
|
||||
|
||||
func (wId *DoormanId) AsJson() string {
|
||||
if wId.Id == "" {
|
||||
return `{}`
|
||||
}
|
||||
return fmt.Sprintf(`{"%v":"%v"}`, wId.Id.Hex(), wId.Name)
|
||||
}
|
||||
|
||||
type DoormanDefinition struct {
|
||||
Id bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Values []*DoormanValue `json:"values" bson:"values"`
|
||||
}
|
||||
|
||||
func NewDoormanDefinition(name string) *DoormanDefinition {
|
||||
return &DoormanDefinition{Id: bson.NewObjectId(), Name: name}
|
||||
}
|
||||
|
||||
// a way to quickly create doormen in tests. do not use in real code. will
|
||||
// panic if a doorman is wrongly specify
|
||||
func QuickNewDoormanDefinition(name string, probs ...float64) *DoormanDefinition {
|
||||
ret := NewDoormanDefinition(name)
|
||||
for i, prob := range probs {
|
||||
ret.Values = append(ret.Values, &DoormanValue{"T" + strconv.Itoa(i), prob})
|
||||
}
|
||||
if err := ret.Validate(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) ValidateDoormanValueNames() error {
|
||||
m := make(map[string]bool)
|
||||
for _, w := range dmd.Values {
|
||||
if name := w.Name; name == "" {
|
||||
return errors.New("doorman value cannot be empty")
|
||||
} else {
|
||||
m[name] = true
|
||||
}
|
||||
}
|
||||
if len(m) != len(dmd.Values) {
|
||||
return errors.New("doorman value names must be unique within a doorman")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) ValidateDoormanValueProbabilities() error {
|
||||
var sum float64 = 0
|
||||
for _, w := range dmd.Values {
|
||||
if p := w.Probability; p < 0-epsilon || p > 1+epsilon {
|
||||
return errors.New(fmt.Sprintf("doorman value %v is out of range", p))
|
||||
} else {
|
||||
sum += p
|
||||
}
|
||||
}
|
||||
if sum < 0-epsilon || sum > 1+epsilon {
|
||||
return errors.New("the sum of the probability must be 1.0")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) Validate() (err error) {
|
||||
switch {
|
||||
case dmd.Name == "":
|
||||
err = errors.New("name cannot be empty")
|
||||
case len(dmd.Values) < 2:
|
||||
err = errors.New("need at least 2 values")
|
||||
case dmd.ValidateDoormanValueNames() != nil:
|
||||
err = dmd.ValidateDoormanValueNames()
|
||||
case dmd.ValidateDoormanValueProbabilities() != nil:
|
||||
err = dmd.ValidateDoormanValueProbabilities()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) CanBeUpdatedBy(dmd2 *DoormanDefinition) error {
|
||||
if dmd.Name != dmd2.Name {
|
||||
return errors.New("cannot change doorman name")
|
||||
}
|
||||
if len(dmd.Values) != len(dmd2.Values) {
|
||||
return errors.New("cannot change the number of cases in doorman")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dmd *DoormanDefinition) AsDoormanUpdatePayload() ([]byte, error) {
|
||||
wu := &shared.DoormanUpdater{Id: dmd.Id.Hex()}
|
||||
wu.Timestamp = time.Now().Unix()
|
||||
wu.Probabilities = make([]float64, len(dmd.Values))
|
||||
for i, value := range dmd.Values {
|
||||
wu.Probabilities[i] = value.Probability
|
||||
}
|
||||
return json.Marshal(wu)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package doormen
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/didiercrunch/doorman/shared"
|
||||
"gopkg.in/mgo.v2/bson"
|
||||
)
|
||||
|
||||
func TestNewDoormanDefinition(t *testing.T) {
|
||||
dmd := NewDoormanDefinition("some_name")
|
||||
if dmd.Id == "" {
|
||||
t.Error("need id at all time")
|
||||
}
|
||||
if dmd.Name != "some_name" {
|
||||
t.Error("bad name")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
dmd := &DoormanDefinition{Name: ""}
|
||||
if err := dmd.Validate(); err.Error() != "name cannot be empty" {
|
||||
t.Error(err)
|
||||
}
|
||||
dmd = &DoormanDefinition{Name: "foo"}
|
||||
if err := dmd.Validate(); err.Error() != "need at least 2 values" {
|
||||
t.Error(err)
|
||||
}
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{nil}}
|
||||
if err := dmd.Validate(); err.Error() != "need at least 2 values" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"foo", 0}}}
|
||||
if err := dmd.Validate(); err.Error() != "doorman value names must be unique within a doorman" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0}, &DoormanValue{"", 0}}}
|
||||
if err := dmd.Validate(); err.Error() != "doorman value cannot be empty" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", -0.25}, &DoormanValue{"bar", 0.5}}}
|
||||
if err := dmd.Validate(); err.Error() != "doorman value -0.25 is out of range" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 2.5}, &DoormanValue{"bar", 0.5}}}
|
||||
if err := dmd.Validate(); err.Error() != "doorman value 2.5 is out of range" {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
dmd = &DoormanDefinition{Name: "foo", Values: []*DoormanValue{&DoormanValue{"foo", 0.75}, &DoormanValue{"bar", 0.5}}}
|
||||
if err := dmd.Validate(); err.Error() != "the sum of the probability must be 1.0" {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsJson(t *testing.T) {
|
||||
id := &DoormanId{}
|
||||
if `{}` != id.AsJson() {
|
||||
t.Fail()
|
||||
}
|
||||
id.Name = "bob"
|
||||
if `{}` != id.AsJson() {
|
||||
t.Fail()
|
||||
}
|
||||
|
||||
id.Id = bson.ObjectIdHex("54af464af6fb5e20c4000003")
|
||||
if `{"54af464af6fb5e20c4000003":"bob"}` != id.AsJson() {
|
||||
t.Error(id.AsJson())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAsDoormanUpdatePayload(t *testing.T) {
|
||||
wd := &DoormanDefinition{}
|
||||
wd.Id = bson.NewObjectId()
|
||||
wd.Values = []*DoormanValue{&DoormanValue{"C", 0.25}, &DoormanValue{"T1", 0.75}}
|
||||
data, err := wd.AsDoormanUpdatePayload()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
wu := &shared.DoormanUpdater{}
|
||||
if err := json.Unmarshal(data, wu); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if wd.Id.Hex() != wu.Id {
|
||||
t.Error("bad id", wu.Id)
|
||||
}
|
||||
if len(wu.Probabilities) != 2 {
|
||||
t.Error("bad probability length")
|
||||
} else if math.Abs(wu.Probabilities[0]-0.25) > epsilon {
|
||||
t.Error("bad first probability")
|
||||
} else if math.Abs(wu.Probabilities[1]-0.75) > epsilon {
|
||||
t.Error("bad second probability")
|
||||
} else if wu.Timestamp == 0 {
|
||||
t.Error("no time specify")
|
||||
} else if wu.Timestamp < 1421537442 {
|
||||
t.Error("timestamp looks weird")
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuickNewDoormanDefinition(t *testing.T) {
|
||||
dmd := QuickNewDoormanDefinition("foo", 0.75, 0.25)
|
||||
if dmd.Name != "foo" {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if len(dmd.Values) != 2 {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if dmd.Values[0].Name != "T0" || dmd.Values[0].Probability != 0.75 {
|
||||
t.Error()
|
||||
}
|
||||
|
||||
if dmd.Values[1].Name != "T1" || dmd.Values[1].Probability != 0.25 {
|
||||
t.Error()
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user