From cc618ddef5a752fd84b8c411ba5ce3cb02c99bf3 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Sun, 14 Feb 2021 17:32:13 +0100 Subject: [PATCH 01/15] add test for full check --- bridge.go | 21 ++++- bridge_test.go | 244 +++++++++++++++++++++++++++++-------------------- 2 files changed, 166 insertions(+), 99 deletions(-) diff --git a/bridge.go b/bridge.go index 51c2229..fd8fd73 100644 --- a/bridge.go +++ b/bridge.go @@ -358,6 +358,23 @@ func (b *Bridge) GetActiveCalls() ([]Call, error) { return calls, err } +// GetAllCalls returns a list of all calls +func (b *Bridge) GetAllCalls() ([]Call, error) { + + // Query the database, storing results in a []User (wrapped in []interface{}) + calls := []Call{} + // b.db.Select(&calls, "SELECT * FROM calls ORDER BY time_start ASC")j + err := b.db.Select(&calls, "SELECT * FROM calls") + + if err != nil { + log.Error(err) + return calls, err + } + + log.Debugf("Found calls: %+v\n", calls) + return calls, err +} + // GetNextPersonsForCall finds the next `num` persons that should be notified // for a callID. Selection is based on group_num //TODO FIXME @@ -434,8 +451,10 @@ func (b *Bridge) GetPersons() ([]Person, error) { // CallFull is true if the call is full. Used to check call status func (b *Bridge) CallFull(call Call) (bool, error) { + var numAccpets int - err := b.db.Get(numAccpets, "select count(id) from invitations where call_id=$1 and status='accepted'", call.ID) + err := b.db.Get(&numAccpets, "select count(id) from invitations where call_id=$1 and status='accepted'", call.ID) + log.Debugf("Checking full status for call %v: %v/%v", call.ID, numAccpets, call.Capacity) return numAccpets >= call.Capacity, err } diff --git a/bridge_test.go b/bridge_test.go index 3717426..c5c1050 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -17,6 +17,49 @@ import ( var ( fixtures *testfixtures.Loader sender *TwillioSender + loc = time.FixedZone("+0100", 3600) + + fixtureCalls []Call = []Call{ + { + ID: 1, + Title: "Call number 1", + Capacity: 1, + TimeStart: time.Date(2021, time.February, 10, 12, 30, 0, 0, loc), + TimeEnd: time.Date(2021, time.February, 10, 12, 35, 0, 0, loc), + YoungOnly: true, + LocName: "loc_name1", + LocStreet: "loc_street1", + LocHouseNr: "loc_housenr1", + LocPLZ: "loc_plz1", + LocCity: "loc_city1", + LocOpt: "loc_opt1", + }, + { + ID: 2, + Title: "Call number 2", + Capacity: 2, + TimeStart: time.Date(2021, time.February, 10, 12, 31, 0, 0, loc), + TimeEnd: time.Date(2021, time.February, 10, 12, 36, 0, 0, loc), + LocName: "loc_name2", + LocStreet: "loc_street2", + LocHouseNr: "loc_housenr2", + LocPLZ: "loc_plz2", + LocCity: "loc_city2", + }, + { + ID: 3, + Title: "Call number 3", + Capacity: 3, + TimeStart: time.Date(2021, time.January, 1, 12, 30, 0, 0, loc), + TimeEnd: time.Date(2021, time.January, 1, 12, 35, 0, 0, loc), + LocName: "loc_name3", + LocStreet: "loc_street3", + LocHouseNr: "loc_housenr3", + LocPLZ: "loc_plz3", + LocCity: "loc_city3", + LocOpt: "loc_opt3", + }, + } ) func TestMain(m *testing.M) { @@ -26,6 +69,11 @@ func TestMain(m *testing.M) { monkey.Patch(time.Now, func() time.Time { return time.Date(2021, 1, 1, 20, 0, 0, 0, time.UTC) }) fmt.Println("Time is now ", time.Now()) + os.Exit(m.Run()) +} + +func prepareTestDatabase() { + var err error if _, err := os.Stat("./test.db"); err == nil { @@ -51,10 +99,8 @@ func TestMain(m *testing.M) { db.MustExec(schemaUsers) db.MustExec(schemaNotifications) - fmt.Println("creating sender") sender = NewTwillioSender("test", "test", "test", "test") - fmt.Println("creating bridge") bridge = &Bridge{ db: db, sender: sender, @@ -63,8 +109,6 @@ func TestMain(m *testing.M) { // Open connection to the test database. // Do NOT import fixtures in a production database! // Existing data would be deleted. - - fmt.Println("creating fixtures") fixtures, err = testfixtures.New( testfixtures.Database(db.DB), // You database connection testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" @@ -76,10 +120,6 @@ func TestMain(m *testing.M) { panic(err) } - os.Exit(m.Run()) -} - -func prepareTestDatabase() { if err := fixtures.Load(); err != nil { fmt.Println("Loading fixtures") panic(err) @@ -255,8 +295,6 @@ func TestBridge_GetCallStatus(t *testing.T) { prepareTestDatabase() - loc := time.FixedZone("myzone", 3600) - tests := []struct { name string id string @@ -267,21 +305,7 @@ func TestBridge_GetCallStatus(t *testing.T) { name: "Get a valid callstatus", id: "1", want: CallStatus{ - Call: Call{ - ID: 1, - Title: "Call number 1", - CenterID: 0, - Capacity: 1, - TimeStart: time.Date(2021, time.February, 10, 12, 30, 0, 0, loc), - TimeEnd: time.Date(2021, time.February, 10, 12, 35, 0, 0, loc), - LocName: "loc_name1", - LocStreet: "loc_street1", - LocHouseNr: "loc_housenr1", - YoungOnly: true, - LocPLZ: "loc_plz1", - LocCity: "loc_city1", - LocOpt: "loc_opt1", - }, + Call: fixtureCalls[0], Persons: []Person{ {"1230", 0, 1, false}, {"1231", 0, 1, false}, @@ -309,8 +333,6 @@ func TestBridge_GetActiveCalls(t *testing.T) { prepareTestDatabase() - loc := time.FixedZone("myzone", 3600) - tests := []struct { name string want []Call @@ -319,33 +341,8 @@ func TestBridge_GetActiveCalls(t *testing.T) { { name: "Get two active calls", want: []Call{ - - { - ID: 1, - Title: "Call number 1", - Capacity: 1, - TimeStart: time.Date(2021, time.February, 10, 12, 30, 0, 0, loc), - TimeEnd: time.Date(2021, time.February, 10, 12, 35, 0, 0, loc), - YoungOnly: true, - LocName: "loc_name1", - LocStreet: "loc_street1", - LocHouseNr: "loc_housenr1", - LocPLZ: "loc_plz1", - LocCity: "loc_city1", - LocOpt: "loc_opt1", - }, - { - ID: 2, - Title: "Call number 2", - Capacity: 2, - TimeStart: time.Date(2021, time.February, 10, 12, 31, 0, 0, loc), - TimeEnd: time.Date(2021, time.February, 10, 12, 36, 0, 0, loc), - LocName: "loc_name2", - LocStreet: "loc_street2", - LocHouseNr: "loc_housenr2", - LocPLZ: "loc_plz2", - LocCity: "loc_city2", - }, + fixtureCalls[0], + fixtureCalls[1], }, wantErr: false, }, @@ -418,24 +415,32 @@ func TestNewBridge(t *testing.T) { } func TestBridge_DeleteOldCalls(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } tests := []struct { - name string - fields fields + name string + want []Call }{ - // TODO: Add test cases. + { + name: "Get all calls after running deletion", + want: []Call{ + fixtureCalls[0], + fixtureCalls[1], + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - b.DeleteOldCalls() + bridge.DeleteOldCalls() }) + + got, err := bridge.GetAllCalls() + if err != nil { + t.Errorf("Bridge.GetAllCalls() error = %v", err) + return + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.GetActive() mismatch (-want +got):\n%s", diff) + } } } @@ -462,29 +467,21 @@ func TestBridge_SendNotifications(t *testing.T) { } func TestBridge_NotifyCall(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - id int - numPersons int - } + + prepareTestDatabase() + tests := []struct { - name string - fields fields - args args - wantErr bool + name string + callID int + numPersons int + wantErr bool }{ + // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - if err := b.NotifyCall(tt.args.id, tt.args.numPersons); (err != nil) != tt.wantErr { + if err := bridge.NotifyCall(tt.callID, tt.numPersons); (err != nil) != tt.wantErr { t.Errorf("Bridge.NotifyCall() error = %v, wantErr %v", err, tt.wantErr) } }) @@ -492,29 +489,21 @@ func TestBridge_NotifyCall(t *testing.T) { } func TestBridge_CallFull(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - call Call - } + prepareTestDatabase() + tests := []struct { name string - fields fields - args args + call Call want bool wantErr bool }{ - // TODO: Add test cases. + {"Get full call (ID:1)", fixtureCalls[0], true, false}, + {"Get not full call (ID:2)", fixtureCalls[1], false, false}, + {"Get not full call (ID:3)", fixtureCalls[2], false, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - got, err := b.CallFull(tt.args.call) + got, err := bridge.CallFull(tt.call) if (err != nil) != tt.wantErr { t.Errorf("Bridge.CallFull() error = %v, wantErr %v", err, tt.wantErr) return @@ -647,3 +636,62 @@ func TestBridge_PersonDelete(t *testing.T) { }) } } + +func TestBridge_AddCall(t *testing.T) { + type fields struct { + db *sqlx.DB + sender *TwillioSender + } + type args struct { + call Call + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &Bridge{ + db: tt.fields.db, + sender: tt.fields.sender, + } + if err := b.AddCall(tt.args.call); (err != nil) != tt.wantErr { + t.Errorf("Bridge.AddCall() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestBridge_GetAllCalls(t *testing.T) { + + prepareTestDatabase() + + tests := []struct { + name string + want []Call + wantErr bool + }{ + { + name: "Retrieve calls from DB", + want: fixtureCalls, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := bridge.GetAllCalls() + if (err != nil) != tt.wantErr { + t.Errorf("Bridge.GetAllCalls() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.GetAllCalls() mismatch (-want +got):\n%s", diff) + } + }) + } +} From 4464331b0bf082cdd396d14efc3a3c253d65a4eb Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Sun, 14 Feb 2021 17:51:45 +0100 Subject: [PATCH 02/15] add test for last call, fix sql query --- bridge.go | 8 +++++++- bridge_test.go | 28 +++++++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/bridge.go b/bridge.go index fd8fd73..3d7431c 100644 --- a/bridge.go +++ b/bridge.go @@ -461,7 +461,13 @@ func (b *Bridge) CallFull(call Call) (bool, error) { // LastCallNotified retrieves the last call a person was notified to func (b *Bridge) LastCallNotified(person Person) (Call, error) { lastCallOfPerson := Call{} - err := b.db.Get(&lastCallOfPerson, "select calls.* from calls join invitations where invitations.phone=$1 and invitations.status = \"accepted\" order by invitations.time desc limit 1", person.Phone) + err := b.db.Get(&lastCallOfPerson, + `SELECT * FROM calls + WHERE id = ( + SELECT call_id FROM invitations + WHERE phone=$1 + ORDER BY time DESC + )`, person.Phone) return lastCallOfPerson, err } diff --git a/bridge_test.go b/bridge_test.go index c5c1050..2871fbd 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -516,35 +516,29 @@ func TestBridge_CallFull(t *testing.T) { } func TestBridge_LastCallNotified(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - person Person - } + prepareTestDatabase() + tests := []struct { name string - fields fields - args args + person Person want Call wantErr bool }{ - // TODO: Add test cases. + {"Phone 1230", Person{Phone: "1230"}, fixtureCalls[0], false}, + {"Phone 1231", Person{Phone: "1231"}, fixtureCalls[0], false}, + {"Phone 1232", Person{Phone: "1232"}, fixtureCalls[1], false}, + {"Phone noexist", Person{Phone: "noexist"}, Call{}, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - got, err := b.LastCallNotified(tt.args.person) + got, err := bridge.LastCallNotified(tt.person) if (err != nil) != tt.wantErr { t.Errorf("Bridge.LastCallNotified() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Bridge.LastCallNotified() = %v, want %v", got, tt.want) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.LastCallNotified() mismatch (-want +got):\n%s", diff) } }) } From d1b6347ee30094d1fe0e649c4f2b9fd2e467fc89 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Sun, 14 Feb 2021 18:08:28 +0100 Subject: [PATCH 03/15] add fixturePersons --- bridge_test.go | 93 ++++++++++++++++++++++------------- testdata/fixtures/persons.yml | 4 +- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/bridge_test.go b/bridge_test.go index 2871fbd..7e91a3a 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -60,6 +60,26 @@ var ( LocOpt: "loc_opt3", }, } + fixturePersons []Person = []Person{ + { + Phone: "1230", + CenterID: 0, + Group: 1, + Status: false, + }, + { + Phone: "1231", + CenterID: 0, + Group: 2, + Status: false, + }, + { + Phone: "1232", + CenterID: 0, + Group: 1, + Status: true, + }, + } ) func TestMain(m *testing.M) { @@ -137,12 +157,8 @@ func TestBridge_GetPersons(t *testing.T) { wantErr bool }{ { - name: "Retrieve persons from DB", - want: []Person{ - {Phone: "1230", CenterID: 0, Group: 1, Status: false}, - {Phone: "1231", CenterID: 0, Group: 1, Status: false}, - {Phone: "1232", CenterID: 0, Group: 1, Status: false}, - }, + name: "Retrieve persons from DB", + want: fixturePersons, wantErr: false, }, } @@ -174,8 +190,8 @@ func TestBridge_GetAcceptedPersons(t *testing.T) { name: "Call with accepted invitations", id: 1, want: []Person{ - {Phone: "1230", Group: 1}, - {Phone: "1231", Group: 1}, + fixturePersons[0], + fixturePersons[1], }, wantErr: false, }, @@ -223,9 +239,9 @@ func TestBridge_AddPerson(t *testing.T) { Status: false, }, want: []Person{ - {Phone: "1230", Group: 1}, - {Phone: "1231", Group: 1}, - {Phone: "1232", Group: 1}, + fixturePersons[0], + fixturePersons[1], + fixturePersons[2], {"0001", 0, 1, false}, }, wantErr: false, }, @@ -264,9 +280,10 @@ func TestBridge_AddPersons(t *testing.T) { {"0002", 0, 1, false}, }, want: []Person{ - {Phone: "1230", Group: 1}, - {Phone: "1231", Group: 1}, - {Phone: "1232", Group: 1}, + + fixturePersons[0], + fixturePersons[1], + fixturePersons[2], {"0001", 0, 1, false}, {"0002", 0, 1, false}, }, @@ -307,8 +324,8 @@ func TestBridge_GetCallStatus(t *testing.T) { want: CallStatus{ Call: fixtureCalls[0], Persons: []Person{ - {"1230", 0, 1, false}, - {"1231", 0, 1, false}, + fixturePersons[0], + fixturePersons[1], }, }, wantErr: false, @@ -524,9 +541,9 @@ func TestBridge_LastCallNotified(t *testing.T) { want Call wantErr bool }{ - {"Phone 1230", Person{Phone: "1230"}, fixtureCalls[0], false}, - {"Phone 1231", Person{Phone: "1231"}, fixtureCalls[0], false}, - {"Phone 1232", Person{Phone: "1232"}, fixtureCalls[1], false}, + {"Phone 1230", fixturePersons[0], fixtureCalls[0], false}, + {"Phone 1231", fixturePersons[1], fixtureCalls[0], false}, + {"Phone 1232", fixturePersons[2], fixtureCalls[1], false}, {"Phone noexist", Person{Phone: "noexist"}, Call{}, true}, } for _, tt := range tests { @@ -603,30 +620,36 @@ func TestBridge_PersonCancelCall(t *testing.T) { } func TestBridge_PersonDelete(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - phoneNumber string - } + + prepareTestDatabase() tests := []struct { - name string - fields fields - args args - wantErr bool + name string + phoneNumber string + want []Person + wantErr bool }{ + {"Remove 1230", "1230", []Person{fixturePersons[1], fixturePersons[2]}, false}, + {"Remove 1231", "1231", []Person{fixturePersons[2]}, false}, + {"Remove 1232", "1232", []Person{}, false}, // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - if err := b.PersonDelete(tt.args.phoneNumber); (err != nil) != tt.wantErr { + + if err := bridge.PersonDelete(tt.phoneNumber); (err != nil) != tt.wantErr { t.Errorf("Bridge.PersonDelete() error = %v, wantErr %v", err, tt.wantErr) } + + got, err := bridge.GetPersons() + if (err != nil) != tt.wantErr { + t.Errorf("Bridge.GetPersons() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.GetPersons() after deleting mismatch (-want +got):\n%s", diff) + } + }) } } diff --git a/testdata/fixtures/persons.yml b/testdata/fixtures/persons.yml index 6b0a0c0..ad89a07 100644 --- a/testdata/fixtures/persons.yml +++ b/testdata/fixtures/persons.yml @@ -5,10 +5,10 @@ - phone: "1231" center_id: 0 - group_num: 1 + group_num: 2 status: 0 - phone: "1232" center_id: 0 group_num: 1 - status: 0 + status: 1 From ed9fadacab1c3ddf29fe123cfafa6b138d50e082 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Sun, 14 Feb 2021 18:18:31 +0100 Subject: [PATCH 04/15] cleanup and todos --- bridge_test.go | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/bridge_test.go b/bridge_test.go index 7e91a3a..257768e 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -380,18 +380,10 @@ func TestBridge_GetActiveCalls(t *testing.T) { } func TestBridge_GetNextPersonsForCall(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - num int - callID int - } tests := []struct { name string - fields fields - args args + num int + callID int want []Person wantErr bool }{ @@ -399,11 +391,7 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - got, err := b.GetNextPersonsForCall(tt.args.num, tt.args.callID) + got, err := bridge.GetNextPersonsForCall(tt.num, tt.callID) if (err != nil) != tt.wantErr { t.Errorf("Bridge.GetNextPersonsForCall() error = %v, wantErr %v", err, tt.wantErr) return @@ -411,6 +399,15 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { if !reflect.DeepEqual(got, tt.want) { t.Errorf("Bridge.GetNextPersonsForCall() = %v, want %v", got, tt.want) } + + // TODO check: + // lower group numbers should always go first + // order within one group should be randomized + // return full number of persons if enough in db, not more or less + // return less persons if not enough available in the database + // check if all persons meet only_young criteria + // check not already notified persons are retrieved + // check persons notified for a different call are retrieved }) } } From cd12450cfabd0557bb5b8067644bbe86b3990a0a Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Sun, 14 Feb 2021 21:01:40 +0100 Subject: [PATCH 05/15] add test for selection algo --- bridge.go | 26 +++- bridge_test.go | 165 +++++++++++++++++++++-- person.go | 1 + testdata/fixtures/calls_selectnext.yml | 41 ++++++ testdata/fixtures/persons_selectnext.yml | 54 ++++++++ 5 files changed, 275 insertions(+), 12 deletions(-) create mode 100644 testdata/fixtures/calls_selectnext.yml create mode 100644 testdata/fixtures/persons_selectnext.yml diff --git a/bridge.go b/bridge.go index 3d7431c..2e7625d 100644 --- a/bridge.go +++ b/bridge.go @@ -25,7 +25,8 @@ CREATE TABLE IF NOT EXISTS persons ( phone TEXT PRIMARY KEY, center_id INTEGER NOT NULL, group_num INTEGER NOT NULL, - status INTEGER NOT NULL + status INTEGER NOT NULL, + young INTEGER NOT NULL ); ` var schemaCalls = ` @@ -449,6 +450,29 @@ func (b *Bridge) GetPersons() ([]Person, error) { return persons, err } +type Invitation struct { + ID int `db:"id"` + Phone string `db:"phone"` + CallID int `db:"call_id"` + Status string `db:"status"` + time time.Time `db:"time"` +} + +func (b *Bridge) GetInvitations() ([]Invitation, error) { + + log.Debug("Retrieving invitations") + + invitations := []Invitation{} + err := b.db.Select(&invitations, "SELECT * FROM invitations") + if err != nil { + log.Error(err) + return invitations, err + } + + log.Debugf("Found invitations: %+v\n", invitations) + return invitations, err +} + // CallFull is true if the call is full. Used to check call status func (b *Bridge) CallFull(call Call) (bool, error) { diff --git a/bridge_test.go b/bridge_test.go index 257768e..6c983a0 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "reflect" + "strconv" "testing" "time" @@ -380,14 +381,44 @@ func TestBridge_GetActiveCalls(t *testing.T) { } func TestBridge_GetNextPersonsForCall(t *testing.T) { + + var fixtures *testfixtures.Loader + var err error + var allPersons []Person + + fixtures, err = testfixtures.New( + testfixtures.Database(bridge.db.DB), // You database connection + testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" + testfixtures.Files("./testdata/fixtures/persons_selectnext.yml"), // the directory containing the YAML files + testfixtures.Files("./testdata/fixtures/invitations_selectnext.yml"), // the directory containing the YAML files + testfixtures.Files("./testdata/fixtures/calls_selectnext.yml"), // the directory containing the YAML files + ) + if err != nil { + panic(err) + } + + allPersons, err = bridge.GetPersons() + if err != nil { + panic(err) + } + + if err := fixtures.Load(); err != nil { + fmt.Println("Loading fixtures") + panic(err) + } tests := []struct { name string num int callID int - want []Person wantErr bool }{ - // TODO: Add test cases. + // 11 persons total in fixtures + // call ID 0: no age restriction + // call ID 1: withage restriction + {"Call without age restriction, 5/11 persons", 5, 0, false}, + {"Call without age restriction, 20/11 persons", 20, 0, false}, + {"Call with age restriction, 5/11 persons", 5, 1, false}, + {"Call with age restriction, 20/11 persons", 20, 1, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -396,18 +427,99 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { t.Errorf("Bridge.GetNextPersonsForCall() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Bridge.GetNextPersonsForCall() = %v, want %v", got, tt.want) + + // Get the call with the ID specified + call, err := bridge.GetCallStatus(strconv.Itoa(tt.callID)) + if err != nil { + panic(err) + } + + invitations, err := bridge.GetInvitations() + + // Find highest selected group in results + highestGroup := 0 + for _, v := range got { + if v.Group > highestGroup { + highestGroup = v.Group + } + } + + lowerPersonNum := 0 + // Find how many persons are in the DB with group number under highestGroup + for _, v := range allPersons { + if v.Group > highestGroup { + lowerPersonNum += 1 + } + } + + // Lower group numbers should always go first. If the number of + // persons with group nubmer below the highest returned group is + // greater or equal of to the number we are looking for, the result + // is wrong. A call should first be issued to alle the persons of + // lower groups before trying the next one up + if lowerPersonNum >= tt.num { + t.Error("Bridge.GetNextPersonsForCall selected persons with higher group than necessary") + return + } + + // if diff := cmp.Diff(tt.want, got); diff != "" { + // t.Errorf("Bridge.GetNextPersonsForCall() mismatch (-want +got):\n%s", diff) + // } + + // Check there are no duplicates in the persons returned. We place + // the returned slice in a map, with the phone as key and a + // arbitrary value (1). For each person, we try to get it by + // accessing the map, if it fails, we add it (this is good and + // means the person was not yet in the map). If it succeds we exit + // with an error, this means we tried to access a key (phonenumber) + // that already exists in the map, meaning that it is duplicate + duplicate_frequency := make(map[string]int) + for _, pers := range got { + if _, ok := duplicate_frequency[pers.Phone]; ok { + t.Error("Bridge.GetNextPersonsForCall returned duplicates") + } else { + duplicate_frequency[pers.Phone] = 1 + } + } + + // Check if less than the requested number of persons where + // returned, even though we had enough to choose from + if len(got) < tt.num && len(allPersons) > tt.num { + t.Error("Bridge.GetNextPersonsForCall returned not enough persons, even though available") + return + } + + // Check the number of persons returned does not exist the number requested + if len(got) > tt.num { + t.Error("Bridge.GetNextPersonsForCall returned too many persons") + return + } + + for _, v := range got { + // check none is already vaccinated + if v.Status { + t.Errorf("Bridge.GetNextPersonsForCall returned already vaccinated person Phone: %v\n", v.Phone) + return + } + + // check if all persons meet only_young criteria (no old persons in call for young only) + if !v.Young && call.Call.YoungOnly { + t.Errorf("Bridge.GetNextPersonsForCall returned person not compatible with call: %v\n", v.Phone) + return + } } - // TODO check: - // lower group numbers should always go first - // order within one group should be randomized - // return full number of persons if enough in db, not more or less - // return less persons if not enough available in the database - // check if all persons meet only_young criteria // check not already notified persons are retrieved - // check persons notified for a different call are retrieved + for _, i := range invitations { + for _, p := range got { + if i.Phone == p.Phone && i.CallID == tt.callID { + t.Errorf("Bridge.GetNextPersonsForCall returned phone %s already notified for call: %v\n", p.Phone, tt.callID) + return + } + } + } + + // TODO check: order within one group should be randomized }) } } @@ -709,3 +821,34 @@ func TestBridge_GetAllCalls(t *testing.T) { }) } } + +func TestBridge_GetInvitations(t *testing.T) { + type fields struct { + db *sqlx.DB + sender *TwillioSender + } + tests := []struct { + name string + fields fields + want []Invitation + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &Bridge{ + db: tt.fields.db, + sender: tt.fields.sender, + } + got, err := b.GetInvitations() + if (err != nil) != tt.wantErr { + t.Errorf("Bridge.GetInvitations() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Bridge.GetInvitations() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/person.go b/person.go index 91a121e..1a4f2ef 100644 --- a/person.go +++ b/person.go @@ -16,6 +16,7 @@ type Person struct { CenterID int `db:"center_id"` // ID of center that added this person Group int `db:"group_num"` // Vaccination group Status bool `db:"status"` // Vaccination status + Young bool `db:"young"` // Can be vacciated with any serum } // NewPerson receives the input data and returns a slice of person objects. For diff --git a/testdata/fixtures/calls_selectnext.yml b/testdata/fixtures/calls_selectnext.yml new file mode 100644 index 0000000..be7bdb1 --- /dev/null +++ b/testdata/fixtures/calls_selectnext.yml @@ -0,0 +1,41 @@ +- id: 1 + title: "Call number 1" + center_id: 0 + capacity: 1 + time_start: "2021-02-10 12:30:00+01:00" + time_end: "2021-02-10 12:35:00+01:00" + loc_name: "loc_name1" + young_only: true + loc_street: "loc_street1" + loc_housenr: "loc_housenr1" + loc_plz: "loc_plz1" + loc_city: "loc_city1" + loc_opt: "loc_opt1" + +- id: 2 + title: "Call number 2" + center_id: 0 + capacity: 2 + time_start: "2021-02-10 12:31:00+01:00" + time_end: "2021-02-10 12:36:00+01:00" + young_only: false + loc_name: "loc_name2" + loc_street: "loc_street2" + loc_housenr: "loc_housenr2" + loc_plz: "loc_plz2" + loc_city: "loc_city2" + loc_opt: "" + +- id: 3 + title: "Call number 3" + center_id: 0 + capacity: 3 + time_start: "2021-01-01 12:30:00+01:00" + time_end: "2021-01-01 12:35:00+01:00" + young_only: false + loc_name: "loc_name3" + loc_street: "loc_street3" + loc_housenr: "loc_housenr3" + loc_plz: "loc_plz3" + loc_city: "loc_city3" + loc_opt: "loc_opt3" diff --git a/testdata/fixtures/persons_selectnext.yml b/testdata/fixtures/persons_selectnext.yml new file mode 100644 index 0000000..3630e52 --- /dev/null +++ b/testdata/fixtures/persons_selectnext.yml @@ -0,0 +1,54 @@ +- phone: "0" + center_id: 0 + group_num: 1 + status: 0 + +- phone: "10" + center_id: 0 + group_num: 1 + status: 1 + +- phone: "11" + center_id: 0 + group_num: 1 + status: 0 + +- phone: "1" + center_id: 0 + group_num: 1 + status: 0 + +- phone: "2" + center_id: 0 + group_num: 2 + status: 0 + +- phone: "3" + center_id: 0 + group_num: 2 + status: 0 + +- phone: "4" + center_id: 0 + group_num: 4 + status: 0 + +- phone: "7" + center_id: 0 + group_num: 4 + status: 0 + +- phone: "8" + center_id: 0 + group_num: 4 + status: 0 + +- phone: "5" + center_id: 0 + group_num: 4 + status: 0 + +- phone: "6" + center_id: 1 + group_num: 2 + status: 0 From 07725e551b0479e88bc4a5f90861929be16c232d Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 10:48:19 +0100 Subject: [PATCH 06/15] fix #72 --- bridge.go | 5 +++-- call.go | 24 ++++++++++++++++-------- handler_person.go | 14 +++++++++++++- handler_upload.go | 8 +++++++- person.go | 5 +++-- 5 files changed, 42 insertions(+), 14 deletions(-) diff --git a/bridge.go b/bridge.go index 2e7625d..e3fe24c 100644 --- a/bridge.go +++ b/bridge.go @@ -26,7 +26,7 @@ CREATE TABLE IF NOT EXISTS persons ( center_id INTEGER NOT NULL, group_num INTEGER NOT NULL, status INTEGER NOT NULL, - young INTEGER NOT NULL + age INTEGER NOT NULL ); ` var schemaCalls = ` @@ -37,7 +37,8 @@ CREATE TABLE IF NOT EXISTS calls ( capacity INTEGER NOT NULL, time_start DATETIME NOT NULL, time_end DATETIME NOT NULL, - young_only INTEGER NOT NULL, + age_min INTEGER NOT NULL, + age_max INTEGER NOT NULL, loc_name TEXT NOT NULL, loc_street TEXT NOT NULL, loc_housenr TEXT NOT NULL, diff --git a/call.go b/call.go index 72977e8..2f6ea26 100644 --- a/call.go +++ b/call.go @@ -19,7 +19,8 @@ type Call struct { Capacity int `db:"capacity"` TimeStart time.Time `db:"time_start"` TimeEnd time.Time `db:"time_end"` - YoungOnly bool `db:"young_only"` + AgeMin int `db:"age_min"` + AgeMax int `db:"age_max"` LocName string `db:"loc_name"` LocStreet string `db:"loc_street"` LocHouseNr string `db:"loc_housenr"` @@ -56,6 +57,18 @@ func NewCall(data url.Values) (Call, []string, error) { retError = err } + ageMin, err := strconv.Atoi(data.Get("age_min")) + if err != nil || ageMin < 0 { + errorStrings = append(errorStrings, "Ungültiges Mindestalter") + retError = err + } + + ageMax, err := strconv.Atoi(data.Get("age_max")) + if err != nil || ageMax > 200 { + errorStrings = append(errorStrings, "Ungültiges Höchstalter") + retError = err + } + // Validate start and end times make sense log.Debug("start-time: ", data.Get("start-time")) log.Debug("end-time: ", data.Get("end-time")) @@ -79,7 +92,6 @@ func NewCall(data url.Values) (Call, []string, error) { // Get text fields and check that they are not empty strings var locName, locStreet, locHouseNr, locPlz, locCity, locOpt, title string - var youngOnly bool locName, errorStrings = getFormFieldWithErrors(data, "loc_name", errorStrings) locStreet, errorStrings = getFormFieldWithErrors(data, "loc_street", errorStrings) @@ -89,11 +101,6 @@ func NewCall(data url.Values) (Call, []string, error) { locOpt, errorStrings = getFormFieldWithErrors(data, "loc_opt", errorStrings) title, errorStrings = getFormFieldWithErrors(data, "title", errorStrings) - if youngOnly, err = strconv.ParseBool(data.Get("young_only")); err != nil { - errorStrings = append(errorStrings, "Ungültige Angabe für Impfstoff") - retError = err - } - if len(errorStrings) != 0 { retError = errors.New("Missing input data") } @@ -114,7 +121,8 @@ func NewCall(data url.Values) (Call, []string, error) { LocPLZ: locPlz, LocCity: locCity, LocOpt: locOpt, - YoungOnly: youngOnly, + AgeMin: ageMin, + AgeMax: ageMax, }, errorStrings, retError } diff --git a/handler_person.go b/handler_person.go index b8754ac..cb88182 100644 --- a/handler_person.go +++ b/handler_person.go @@ -44,6 +44,7 @@ func handlerAddPerson(w http.ResponseWriter, r *http.Request) { data := r.Form phone := data.Get("phone") group := data.Get("group") + age := data.Get("age") // Try to create new call from input data groupNum, err := strconv.Atoi(group) @@ -57,6 +58,17 @@ func handlerAddPerson(w http.ResponseWriter, r *http.Request) { return } + ageNum, err := strconv.Atoi(age) + + if err != nil { + log.Debug(err) + tData.AppMessages = append(tData.AppMessages, "Ungültiges Alter") + if err := templates.ExecuteTemplate(w, "importPersons.html", tData); err != nil { + log.Error(err) + } + return + } + if phone == "" { tData.AppMessages = append(tData.AppMessages, "Fehlende Rufnummer") if err := templates.ExecuteTemplate(w, "importPersons.html", tData); err != nil { @@ -65,7 +77,7 @@ func handlerAddPerson(w http.ResponseWriter, r *http.Request) { return } - person, err := NewPerson(0, groupNum, phone, false) + person, err := NewPerson(0, groupNum, phone, false, ageNum) if err != nil { log.Debug(err) tData.AppMessages = append(tData.AppMessages, "Eingaben ungültig") diff --git a/handler_upload.go b/handler_upload.go index d14c1f7..1e15b29 100644 --- a/handler_upload.go +++ b/handler_upload.go @@ -61,9 +61,15 @@ func handlerUpload(w http.ResponseWriter, r *http.Request) { return } + ageNum, err := strconv.Atoi(record[2]) + if err != nil { + log.Warn(err) + return + } + // Try to create a new persion object from the data and return on // errors - p, err := NewPerson(0, groupNum, record[1], false) + p, err := NewPerson(0, groupNum, record[1], false, ageNum) if err != nil { log.Warn(err) return diff --git a/person.go b/person.go index 1a4f2ef..0713a93 100644 --- a/person.go +++ b/person.go @@ -16,17 +16,18 @@ type Person struct { CenterID int `db:"center_id"` // ID of center that added this person Group int `db:"group_num"` // Vaccination group Status bool `db:"status"` // Vaccination status - Young bool `db:"young"` // Can be vacciated with any serum + Age int `db:"age"` // Age of the person, to determine compatible vaccines } // NewPerson receives the input data and returns a slice of person objects. For // single import this will just be an array with a single entry, for CSV upload // it may be longer. -func NewPerson(centerID, group int, phone string, status bool) (Person, error) { +func NewPerson(centerID, group int, phone string, status bool, age int) (Person, error) { person := Person{ CenterID: centerID, Status: status, + Age: age, } num, err := libphonenumber.Parse(phone, "DE") From 60b2e4d39cf8377852df791cd2f97741882884a6 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 10:54:19 +0100 Subject: [PATCH 07/15] add age to test data --- bridge.go | 6 ++++-- testdata/fixtures/calls.yml | 9 ++++++--- testdata/fixtures/calls_selectnext.yml | 9 ++++++--- testdata/fixtures/persons.yml | 3 +++ testdata/fixtures/persons_selectnext.yml | 11 +++++++++++ 5 files changed, 30 insertions(+), 8 deletions(-) diff --git a/bridge.go b/bridge.go index e3fe24c..4eb4528 100644 --- a/bridge.go +++ b/bridge.go @@ -238,7 +238,8 @@ func (b *Bridge) AddCall(call Call) error { capacity, time_start, time_end, - young_only, + age_min, + age_max, loc_name, loc_street, loc_housenr, @@ -251,7 +252,8 @@ func (b *Bridge) AddCall(call Call) error { :capacity, :time_start, :time_end, - :young_only, + :age_min, + :age_max, :loc_name, :loc_street, :loc_housenr, diff --git a/testdata/fixtures/calls.yml b/testdata/fixtures/calls.yml index be7bdb1..df6c0fa 100644 --- a/testdata/fixtures/calls.yml +++ b/testdata/fixtures/calls.yml @@ -5,7 +5,8 @@ time_start: "2021-02-10 12:30:00+01:00" time_end: "2021-02-10 12:35:00+01:00" loc_name: "loc_name1" - young_only: true + age_min: 0 + age_max: 100 loc_street: "loc_street1" loc_housenr: "loc_housenr1" loc_plz: "loc_plz1" @@ -18,7 +19,8 @@ capacity: 2 time_start: "2021-02-10 12:31:00+01:00" time_end: "2021-02-10 12:36:00+01:00" - young_only: false + age_min: 0 + age_max: 100 loc_name: "loc_name2" loc_street: "loc_street2" loc_housenr: "loc_housenr2" @@ -32,7 +34,8 @@ capacity: 3 time_start: "2021-01-01 12:30:00+01:00" time_end: "2021-01-01 12:35:00+01:00" - young_only: false + age_min: 0 + age_max: 100 loc_name: "loc_name3" loc_street: "loc_street3" loc_housenr: "loc_housenr3" diff --git a/testdata/fixtures/calls_selectnext.yml b/testdata/fixtures/calls_selectnext.yml index be7bdb1..466f5ca 100644 --- a/testdata/fixtures/calls_selectnext.yml +++ b/testdata/fixtures/calls_selectnext.yml @@ -5,7 +5,8 @@ time_start: "2021-02-10 12:30:00+01:00" time_end: "2021-02-10 12:35:00+01:00" loc_name: "loc_name1" - young_only: true + age_min: 0 + age_max: 65 loc_street: "loc_street1" loc_housenr: "loc_housenr1" loc_plz: "loc_plz1" @@ -18,7 +19,8 @@ capacity: 2 time_start: "2021-02-10 12:31:00+01:00" time_end: "2021-02-10 12:36:00+01:00" - young_only: false + age_min: 0 + age_max: 100 loc_name: "loc_name2" loc_street: "loc_street2" loc_housenr: "loc_housenr2" @@ -32,7 +34,8 @@ capacity: 3 time_start: "2021-01-01 12:30:00+01:00" time_end: "2021-01-01 12:35:00+01:00" - young_only: false + age_min: 60 + age_max: 100 loc_name: "loc_name3" loc_street: "loc_street3" loc_housenr: "loc_housenr3" diff --git a/testdata/fixtures/persons.yml b/testdata/fixtures/persons.yml index ad89a07..832f501 100644 --- a/testdata/fixtures/persons.yml +++ b/testdata/fixtures/persons.yml @@ -1,14 +1,17 @@ - phone: "1230" center_id: 0 + age: 10 group_num: 1 status: 0 - phone: "1231" center_id: 0 + age: 70 group_num: 2 status: 0 - phone: "1232" center_id: 0 + age: 150 group_num: 1 status: 1 diff --git a/testdata/fixtures/persons_selectnext.yml b/testdata/fixtures/persons_selectnext.yml index 3630e52..9705e89 100644 --- a/testdata/fixtures/persons_selectnext.yml +++ b/testdata/fixtures/persons_selectnext.yml @@ -1,54 +1,65 @@ - phone: "0" center_id: 0 + age: 1 group_num: 1 status: 0 - phone: "10" center_id: 0 + age: 20 group_num: 1 status: 1 - phone: "11" center_id: 0 + age: 40 group_num: 1 status: 0 - phone: "1" center_id: 0 + age: 60 group_num: 1 status: 0 - phone: "2" center_id: 0 + age: 65 group_num: 2 status: 0 - phone: "3" center_id: 0 + age: 70 group_num: 2 status: 0 - phone: "4" center_id: 0 + age: 75 group_num: 4 status: 0 - phone: "7" center_id: 0 + age: 80 group_num: 4 status: 0 - phone: "8" center_id: 0 + age: 85 group_num: 4 status: 0 - phone: "5" center_id: 0 + age: 90 group_num: 4 status: 0 - phone: "6" center_id: 1 + age: 100 group_num: 2 status: 0 From 7ab5968614a50313305cfc5318d47fb5ad24a59c Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 11:00:37 +0100 Subject: [PATCH 08/15] update age tests --- bridge_test.go | 24 +++++++++++++++--------- testdata/fixtures/calls.yml | 6 +++--- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/bridge_test.go b/bridge_test.go index 6c983a0..558a699 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -27,7 +27,8 @@ var ( Capacity: 1, TimeStart: time.Date(2021, time.February, 10, 12, 30, 0, 0, loc), TimeEnd: time.Date(2021, time.February, 10, 12, 35, 0, 0, loc), - YoungOnly: true, + AgeMin: 0, + AgeMax: 100, LocName: "loc_name1", LocStreet: "loc_street1", LocHouseNr: "loc_housenr1", @@ -41,6 +42,8 @@ var ( Capacity: 2, TimeStart: time.Date(2021, time.February, 10, 12, 31, 0, 0, loc), TimeEnd: time.Date(2021, time.February, 10, 12, 36, 0, 0, loc), + AgeMin: 0, + AgeMax: 70, LocName: "loc_name2", LocStreet: "loc_street2", LocHouseNr: "loc_housenr2", @@ -53,6 +56,8 @@ var ( Capacity: 3, TimeStart: time.Date(2021, time.January, 1, 12, 30, 0, 0, loc), TimeEnd: time.Date(2021, time.January, 1, 12, 35, 0, 0, loc), + AgeMin: 70, + AgeMax: 200, LocName: "loc_name3", LocStreet: "loc_street3", LocHouseNr: "loc_housenr3", @@ -238,12 +243,13 @@ func TestBridge_AddPerson(t *testing.T) { CenterID: 0, Group: 1, Status: false, + Age: 80, }, want: []Person{ fixturePersons[0], fixturePersons[1], fixturePersons[2], - {"0001", 0, 1, false}, + {"0001", 0, 1, false, 80}, }, wantErr: false, }, } @@ -277,16 +283,16 @@ func TestBridge_AddPersons(t *testing.T) { { name: "Add two persons", persons: []Person{ - {"0001", 0, 1, false}, - {"0002", 0, 1, false}, + {"0001", 0, 1, false, 40}, + {"0002", 0, 1, false, 90}, }, want: []Person{ fixturePersons[0], fixturePersons[1], fixturePersons[2], - {"0001", 0, 1, false}, - {"0002", 0, 1, false}, + {"0001", 0, 1, false, 40}, + {"0002", 0, 1, false, 90}, }, wantErr: false, }, @@ -502,9 +508,9 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { return } - // check if all persons meet only_young criteria (no old persons in call for young only) - if !v.Young && call.Call.YoungOnly { - t.Errorf("Bridge.GetNextPersonsForCall returned person not compatible with call: %v\n", v.Phone) + // check if all persons age criteria + if v.Age > call.Call.AgeMax || v.Age < call.Call.AgeMin { + t.Errorf("Bridge.GetNextPersonsForCall returned person outside of allowed age range, phone: %v\n", v.Phone) return } } diff --git a/testdata/fixtures/calls.yml b/testdata/fixtures/calls.yml index df6c0fa..ac46cc9 100644 --- a/testdata/fixtures/calls.yml +++ b/testdata/fixtures/calls.yml @@ -20,7 +20,7 @@ time_start: "2021-02-10 12:31:00+01:00" time_end: "2021-02-10 12:36:00+01:00" age_min: 0 - age_max: 100 + age_max: 70 loc_name: "loc_name2" loc_street: "loc_street2" loc_housenr: "loc_housenr2" @@ -34,8 +34,8 @@ capacity: 3 time_start: "2021-01-01 12:30:00+01:00" time_end: "2021-01-01 12:35:00+01:00" - age_min: 0 - age_max: 100 + age_min: 70 + age_max: 200 loc_name: "loc_name3" loc_street: "loc_street3" loc_housenr: "loc_housenr3" From ad6d732576c5d78474e24842395518df2599fa6d Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 12:09:57 +0100 Subject: [PATCH 09/15] add selection sql + test --- bridge.go | 35 ++++++---- bridge_test.go | 15 +++-- .../fixtures/getNextPersonsForCall/calls.yml | 44 +++++++++++++ .../getNextPersonsForCall/invitations.yml | 17 +++++ .../getNextPersonsForCall/persons.yml | 65 +++++++++++++++++++ testdata/fixtures/invitations_selectnext.yml | 17 +++++ 6 files changed, 174 insertions(+), 19 deletions(-) create mode 100644 testdata/fixtures/getNextPersonsForCall/calls.yml create mode 100644 testdata/fixtures/getNextPersonsForCall/invitations.yml create mode 100644 testdata/fixtures/getNextPersonsForCall/persons.yml create mode 100644 testdata/fixtures/invitations_selectnext.yml diff --git a/bridge.go b/bridge.go index 4eb4528..e21a0f3 100644 --- a/bridge.go +++ b/bridge.go @@ -282,8 +282,8 @@ func (b *Bridge) AddPerson(person Person) error { tx := b.db.MustBegin() if res, err = tx.NamedExec( - "INSERT INTO persons (center_id, group_num, phone, status) VALUES "+ - "(:center_id, :group_num, :phone, :status)", &person); err != nil { + "INSERT INTO persons (center_id, group_num, phone, status, age) VALUES "+ + "(:center_id, :group_num, :phone, :status, :age)", &person); err != nil { return err } @@ -306,8 +306,8 @@ func (b *Bridge) AddPersons(persons []Person) error { tx := b.db.MustBegin() for k := range persons { if _, err := tx.NamedExec( - "INSERT INTO persons (center_id, group_num, phone, status) VALUES "+ - "(:center_id, :group_num, :phone, :status)", &persons[k]); err != nil { + "INSERT INTO persons (center_id, group_num, phone, status, age) VALUES "+ + "(:center_id, :group_num, :phone, :status, :age)", &persons[k]); err != nil { return err } @@ -391,19 +391,26 @@ func (b *Bridge) GetNextPersonsForCall(num, callID int) ([]Person, error) { // Get all groups log.Debugf("Retrieving next persons %v for call ID: %v\n", num, callID) + var err error + var call CallStatus + + call, err = bridge.GetCallStatus(strconv.Itoa(callID)) + if err != nil { + return []Person{}, err + } persons := []Person{} - err := b.db.Select(&persons, + err = b.db.Select(&persons, `SELECT * FROM persons - WHERE id NOT IN ( - SELECT id FROM invitations - WHERE status NOT IN ( - "accepted", "notified" - ) - OR call_id !=$1 + WHERE phone NOT IN ( + SELECT phone FROM invitations + WHERE call_id=$1 ) - ORDER BY group_num LIMIT $2`, - callID, num) + AND age<=$2 + AND age>=$3 + AND status=0 + ORDER BY group_num LIMIT $4`, + callID, call.Call.AgeMax, call.Call.AgeMin, num) if err != nil { log.Error(err) return persons, err @@ -458,7 +465,7 @@ type Invitation struct { Phone string `db:"phone"` CallID int `db:"call_id"` Status string `db:"status"` - time time.Time `db:"time"` + Time time.Time `db:"time"` } func (b *Bridge) GetInvitations() ([]Invitation, error) { diff --git a/bridge_test.go b/bridge_test.go index 558a699..1b02a28 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -71,18 +71,21 @@ var ( Phone: "1230", CenterID: 0, Group: 1, + Age: 10, Status: false, }, { Phone: "1231", CenterID: 0, Group: 2, + Age: 70, Status: false, }, { Phone: "1232", CenterID: 0, Group: 1, + Age: 150, Status: true, }, } @@ -393,11 +396,11 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { var allPersons []Person fixtures, err = testfixtures.New( - testfixtures.Database(bridge.db.DB), // You database connection - testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" - testfixtures.Files("./testdata/fixtures/persons_selectnext.yml"), // the directory containing the YAML files - testfixtures.Files("./testdata/fixtures/invitations_selectnext.yml"), // the directory containing the YAML files - testfixtures.Files("./testdata/fixtures/calls_selectnext.yml"), // the directory containing the YAML files + testfixtures.Database(bridge.db.DB), // You database connection + testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" + testfixtures.Files("./testdata/fixtures/getNextPersonsForCall/persons.yml"), // the directory containing the YAML files + testfixtures.Files("./testdata/fixtures/getNextPersonsForCall/invitations.yml"), // the directory containing the YAML files + testfixtures.Files("./testdata/fixtures/getNextPersonsForCall/calls.yml"), // the directory containing the YAML files ) if err != nil { panic(err) @@ -547,6 +550,8 @@ func TestNewBridge(t *testing.T) { } func TestBridge_DeleteOldCalls(t *testing.T) { + + prepareTestDatabase() tests := []struct { name string want []Call diff --git a/testdata/fixtures/getNextPersonsForCall/calls.yml b/testdata/fixtures/getNextPersonsForCall/calls.yml new file mode 100644 index 0000000..466f5ca --- /dev/null +++ b/testdata/fixtures/getNextPersonsForCall/calls.yml @@ -0,0 +1,44 @@ +- id: 1 + title: "Call number 1" + center_id: 0 + capacity: 1 + time_start: "2021-02-10 12:30:00+01:00" + time_end: "2021-02-10 12:35:00+01:00" + loc_name: "loc_name1" + age_min: 0 + age_max: 65 + loc_street: "loc_street1" + loc_housenr: "loc_housenr1" + loc_plz: "loc_plz1" + loc_city: "loc_city1" + loc_opt: "loc_opt1" + +- id: 2 + title: "Call number 2" + center_id: 0 + capacity: 2 + time_start: "2021-02-10 12:31:00+01:00" + time_end: "2021-02-10 12:36:00+01:00" + age_min: 0 + age_max: 100 + loc_name: "loc_name2" + loc_street: "loc_street2" + loc_housenr: "loc_housenr2" + loc_plz: "loc_plz2" + loc_city: "loc_city2" + loc_opt: "" + +- id: 3 + title: "Call number 3" + center_id: 0 + capacity: 3 + time_start: "2021-01-01 12:30:00+01:00" + time_end: "2021-01-01 12:35:00+01:00" + age_min: 60 + age_max: 100 + loc_name: "loc_name3" + loc_street: "loc_street3" + loc_housenr: "loc_housenr3" + loc_plz: "loc_plz3" + loc_city: "loc_city3" + loc_opt: "loc_opt3" diff --git a/testdata/fixtures/getNextPersonsForCall/invitations.yml b/testdata/fixtures/getNextPersonsForCall/invitations.yml new file mode 100644 index 0000000..7883fd6 --- /dev/null +++ b/testdata/fixtures/getNextPersonsForCall/invitations.yml @@ -0,0 +1,17 @@ +- id: 0 + phone: "1230" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 1 + phone: "1231" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 2 + phone: "1232" + call_id: 2 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" diff --git a/testdata/fixtures/getNextPersonsForCall/persons.yml b/testdata/fixtures/getNextPersonsForCall/persons.yml new file mode 100644 index 0000000..9705e89 --- /dev/null +++ b/testdata/fixtures/getNextPersonsForCall/persons.yml @@ -0,0 +1,65 @@ +- phone: "0" + center_id: 0 + age: 1 + group_num: 1 + status: 0 + +- phone: "10" + center_id: 0 + age: 20 + group_num: 1 + status: 1 + +- phone: "11" + center_id: 0 + age: 40 + group_num: 1 + status: 0 + +- phone: "1" + center_id: 0 + age: 60 + group_num: 1 + status: 0 + +- phone: "2" + center_id: 0 + age: 65 + group_num: 2 + status: 0 + +- phone: "3" + center_id: 0 + age: 70 + group_num: 2 + status: 0 + +- phone: "4" + center_id: 0 + age: 75 + group_num: 4 + status: 0 + +- phone: "7" + center_id: 0 + age: 80 + group_num: 4 + status: 0 + +- phone: "8" + center_id: 0 + age: 85 + group_num: 4 + status: 0 + +- phone: "5" + center_id: 0 + age: 90 + group_num: 4 + status: 0 + +- phone: "6" + center_id: 1 + age: 100 + group_num: 2 + status: 0 diff --git a/testdata/fixtures/invitations_selectnext.yml b/testdata/fixtures/invitations_selectnext.yml new file mode 100644 index 0000000..7883fd6 --- /dev/null +++ b/testdata/fixtures/invitations_selectnext.yml @@ -0,0 +1,17 @@ +- id: 0 + phone: "1230" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 1 + phone: "1231" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 2 + phone: "1232" + call_id: 2 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" From ca5fb6e6141cf28bd40b8c7fd22f5516db242db0 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 13:14:08 +0100 Subject: [PATCH 10/15] check error in tests --- bridge_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bridge_test.go b/bridge_test.go index 1b02a28..6334bed 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -444,6 +444,9 @@ func TestBridge_GetNextPersonsForCall(t *testing.T) { } invitations, err := bridge.GetInvitations() + if err != nil { + panic(err) + } // Find highest selected group in results highestGroup := 0 @@ -552,6 +555,7 @@ func TestNewBridge(t *testing.T) { func TestBridge_DeleteOldCalls(t *testing.T) { prepareTestDatabase() + tests := []struct { name string want []Call From c03413ce99c9dd2832e0ce01332079eebcc22617 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 13:19:37 +0100 Subject: [PATCH 11/15] add test for getInvitations() --- bridge_test.go | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/bridge_test.go b/bridge_test.go index 6334bed..acd12c8 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -89,6 +89,29 @@ var ( Status: true, }, } + + fixtureInvitations []Invitation = []Invitation{ + { + Phone: "1230", + CallID: 1, + Status: "accepted", + Time: time.Date(2021, 2, 10, 12, 36, 0, 0, loc), + }, + { + ID: 1, + Phone: "1231", + CallID: 1, + Status: "accepted", + Time: time.Date(2021, 2, 10, 12, 36, 0, 0, loc), + }, + { + ID: 2, + Phone: "1232", + CallID: 2, + Status: "rejected", + Time: time.Date(2021, 2, 10, 12, 36, 0, 0, loc), + }, + } ) func TestMain(m *testing.M) { @@ -838,31 +861,23 @@ func TestBridge_GetAllCalls(t *testing.T) { } func TestBridge_GetInvitations(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } tests := []struct { name string - fields fields want []Invitation wantErr bool }{ - // TODO: Add test cases. + {"Retrieve all invitations from DB", fixtureInvitations, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - got, err := b.GetInvitations() + got, err := bridge.GetInvitations() if (err != nil) != tt.wantErr { t.Errorf("Bridge.GetInvitations() error = %v, wantErr %v", err, tt.wantErr) return } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Bridge.GetInvitations() = %v, want %v", got, tt.want) + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.GetInvitations() mismatch (-want +got):\n%s", diff) } }) } From 9802327c75a8f7dd4eb2a9068304803c6d0b3c46 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 20:01:48 +0100 Subject: [PATCH 12/15] mock http client for tests --- bridge.go | 118 +++++++--- bridge_test.go | 201 ++++++++++++++---- go.sum | 1 + helper.go | 3 +- main.go | 34 +-- sender.go | 14 +- .../TestBridge_DeleteOldCalls/calls.yml | 44 ++++ .../TestBridge_GetActiveCalls/calls.yml | 44 ++++ .../calls.yml} | 8 +- .../invitations.yml | 42 ++++ .../TestBridge_PersonAcceptLastCall/calls.yml | 46 ++++ .../invitations.yml | 29 +++ testdata/fixtures/invitations_selectnext.yml | 17 -- testdata/fixtures/persons_selectnext.yml | 65 ------ 14 files changed, 484 insertions(+), 182 deletions(-) create mode 100644 testdata/fixtures/TestBridge_DeleteOldCalls/calls.yml create mode 100644 testdata/fixtures/TestBridge_GetActiveCalls/calls.yml rename testdata/fixtures/{calls_selectnext.yml => TestBridge_LastCallNotified/calls.yml} (95%) create mode 100644 testdata/fixtures/TestBridge_LastCallNotified/invitations.yml create mode 100644 testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml create mode 100644 testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml delete mode 100644 testdata/fixtures/invitations_selectnext.yml delete mode 100644 testdata/fixtures/persons_selectnext.yml diff --git a/bridge.go b/bridge.go index e21a0f3..1dafc26 100644 --- a/bridge.go +++ b/bridge.go @@ -70,11 +70,12 @@ CREATE TABLE IF NOT EXISTS invitations ( func NewBridge() *Bridge { log.Info("Creating new bridge") - log.Info("Using database:", dbPath) + + log.Info("Using database:", os.Getenv("IMPF_DB_FILE")) // Open connection to database file. Will be created if it does not already // exist. Exit application on errors, we can't continue without database - db, err := sqlx.Connect("sqlite3", dbPath) + db, err := sqlx.Connect("sqlite3", os.Getenv("IMPF_DB_FILE")) // Only required because of a bug with sqlx and sqlite. // TODO remove when migrating to postgresql if performance is too bad @@ -461,25 +462,34 @@ func (b *Bridge) GetPersons() ([]Person, error) { } type Invitation struct { - ID int `db:"id"` - Phone string `db:"phone"` - CallID int `db:"call_id"` - Status string `db:"status"` - Time time.Time `db:"time"` + ID int `db:"id"` + Phone string `db:"phone"` + CallID int `db:"call_id"` + Status invitationStatus `db:"status"` + Time time.Time `db:"time"` } +type invitationStatus string + +const ( + InvitationAccepted invitationStatus = "accepted" + InvitationRejected = "rejected" + InvitationCancelled = "cancelled" + InvitationNotified = "notified" +) + func (b *Bridge) GetInvitations() ([]Invitation, error) { log.Debug("Retrieving invitations") invitations := []Invitation{} - err := b.db.Select(&invitations, "SELECT * FROM invitations") + err := b.db.Select(&invitations, "SELECT * FROM invitations ORDER BY time DESC") if err != nil { log.Error(err) return invitations, err } - log.Debugf("Found invitations: %+v\n", invitations) + // log.Debugf("Found invitations: %+v\n", invitations) return invitations, err } @@ -512,49 +522,86 @@ func (b *Bridge) PersonAcceptLastCall(phoneNumber string) error { log.Debugf("number %s trying to accept call\n", phoneNumber) - lastCall, err := b.LastCallNotified(Person{Phone: phoneNumber}) + var err error + var lastCall Call + var isFull bool - if err != nil { + if lastCall, err = b.LastCallNotified(Person{Phone: phoneNumber}); err != nil { + log.Debugf("Phone %s has not been invited yet\n", phoneNumber) return err } - isFull, err := b.CallFull(lastCall) - - if err != nil { + if isFull, err = b.CallFull(lastCall); err != nil { + log.Debugf("Last call %v, does not exist\n", lastCall.ID) return err } if isFull { - log.Debugf("number %s rejected for call (is full)\n", phoneNumber) + log.Debugf("Rejecting number %s for call (is full)\n", phoneNumber) if err := b.sender.SendMessageReject(phoneNumber); err != nil { + log.Errorf("Failed to send reject message for phone %s\n", phoneNumber) log.Error(err) } } else { log.Debugf("Accepting number %s for call \n", phoneNumber) - if err = b.sender.SendMessageAccept( - phoneNumber, - lastCall.TimeStart.Format("14:12"), - lastCall.TimeEnd.Format("14:12"), - lastCall.LocName, - lastCall.LocStreet, - lastCall.LocHouseNr, - lastCall.LocPLZ, - lastCall.LocCity, - lastCall.LocOpt, - genOTP(phoneNumber, lastCall.ID), - ); err != nil { - return err - } - - _, err = bridge.db.NamedExec( - `UPDATE invitations SET status = "accepted", time=:time WHERE phone=:phone `, //TODO Test + log.Debugf("Setting status=accepted for phone %s\n", phoneNumber) + + var res sql.Result + res, err = bridge.db.NamedExec( + `UPDATE invitations SET + status=:status, + time=:time + WHERE + phone=:phone + AND call_id=:call_id + AND status=:oldstatus`, map[string]interface{}{ - "phone": phoneNumber, - "time": time.Now(), + "status": InvitationAccepted, + "oldstatus": InvitationNotified, + "phone": phoneNumber, + "time": time.Now(), + "call_id": lastCall.ID, }, ) + + if err != nil { + log.Errorf("Failed to set accepted status for last invitation of %s\n", phoneNumber) + return err + } + + rowNum, err := res.RowsAffected() + + if err != nil { + log.Error("Failed to get number of affected invitations") + return err + } + + log.Debugf("Updated %v invitations\n", rowNum) + + if rowNum == 0 { + log.Warn("No invitations updated, call might have been already accepted") + } else { + + log.Debugf("Sending accept message to phone %s\n", phoneNumber) + + if err = b.sender.SendMessageAccept( + phoneNumber, + lastCall.TimeStart.Format("14:12"), + lastCall.TimeEnd.Format("14:12"), + lastCall.LocName, + lastCall.LocStreet, + lastCall.LocHouseNr, + lastCall.LocPLZ, + lastCall.LocCity, + lastCall.LocOpt, + genOTP(phoneNumber, lastCall.ID), + ); err != nil { + log.Errorf("Failed to send accept message for phone %s: %v\n", phoneNumber, err) + return err + } + } } return err @@ -586,15 +633,18 @@ func (b *Bridge) PersonDelete(phoneNumber string) error { result, err := b.db.NamedExec("DELETE FROM persons WHERE phone=:phone", m) if err != nil { + log.Warnf("Phone %s not deleted %v\n", phoneNumber, err) return err } numrows, err := result.RowsAffected() if err != nil { + log.Warnf("Failed to get rows affected by deletion %v\n", err) return err } if err := b.sender.SendMessageDelete(phoneNumber); err != nil { + log.Warnf("Failed to send deletion confirmation to %s: %v\n", phoneNumber, err) return err } diff --git a/bridge_test.go b/bridge_test.go index acd12c8..988a492 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -1,10 +1,13 @@ package main import ( + "bytes" "fmt" + "io/ioutil" "os" "reflect" "strconv" + "strings" "testing" "time" @@ -13,8 +16,20 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" + "net/http" ) +// Custom type that allows setting the func that our Mock Do func will run +// instead +type MockClient struct { + MockDo func(req *http.Request) (*http.Response, error) // MockClient is the mock client +} + +// Overriding what the Do function should "do" in our MockClient +func (m *MockClient) Do(req *http.Request) (*http.Response, error) { + return m.MockDo(req) +} + var ( fixtures *testfixtures.Loader sender *TwillioSender @@ -114,17 +129,63 @@ var ( } ) +var HTTPResponse string + +// formatRequest generates ascii representation of a request +func formatRequest(r *http.Request) string { + // Create return string + var request []string // Add the request string + url := fmt.Sprintf("%v %v %v", r.Method, r.URL, r.Proto) + request = append(request, url) // Add the host + request = append(request, fmt.Sprintf("Host: %v", r.Host)) // Loop through headers + for name, headers := range r.Header { + name = strings.ToLower(name) + for _, h := range headers { + request = append(request, fmt.Sprintf("%v: %v", name, h)) + } + } + + // If this is a POST, add post data + if r.Method == "POST" { + r.ParseForm() + // request = append(request, "Formdata:") + request = append(request, r.Form.Encode()) + } // Return the request as a string + return strings.Join(request, "\n") +} + func TestMain(m *testing.M) { + Client = &MockClient{ + MockDo: func(req *http.Request) (*http.Response, error) { + fmt.Printf("Faking request: \n%s\n", formatRequest(req)) + fmt.Printf("Faking response: %s\n", HTTPResponse) + return &http.Response{ + StatusCode: 200, + Body: ioutil.NopCloser(bytes.NewReader([]byte(HTTPResponse))), + }, nil + }, + } + // Fix current time inside tests to: // 2021-01-01 20:00:00 +0000 UTC - monkey.Patch(time.Now, func() time.Time { return time.Date(2021, 1, 1, 20, 0, 0, 0, time.UTC) }) - fmt.Println("Time is now ", time.Now()) + monkey.Patch(time.Now, func() time.Time { return time.Date(2999, 1, 1, 20, 0, 0, 0, time.UTC) }) + fmt.Println("Time is now fixed to:", time.Now()) os.Exit(m.Run()) } -func prepareTestDatabase() { +func prepareTestDatabase(fixtureFiles ...string) { + + os.Setenv("IMPF_DISABLE_SMS", "") + os.Setenv("IMPF_MODE", "DEVEL") + os.Setenv("IMPF_SESSION_SECRET", "session_secret") + os.Setenv("IMPF_TWILIO_API_ENDPOINT", "https://studio.twilio.com/v2/Flows/") + os.Setenv("IMPF_TWILIO_API_FROM", "twilio_api_from") + os.Setenv("IMPF_TWILIO_API_PASS", "twilio_api_pass") + os.Setenv("IMPF_TWILIO_API_USER", "twilio_api_user") + os.Setenv("IMPF_TWILIO_PASS", "twilio_pass") + os.Setenv("IMPF_TWILIO_USER", "twilio_user") var err error @@ -151,7 +212,12 @@ func prepareTestDatabase() { db.MustExec(schemaUsers) db.MustExec(schemaNotifications) - sender = NewTwillioSender("test", "test", "test", "test") + sender = NewTwillioSender( + os.Getenv("IMPF_TWILIO_API_ENDPOINT"), + os.Getenv("IMPF_TWILIO_API_USER"), + os.Getenv("IMPF_TWILIO_API_PASS"), + os.Getenv("IMPF_TWILIO_API_FROM"), + ) bridge = &Bridge{ db: db, @@ -161,12 +227,20 @@ func prepareTestDatabase() { // Open connection to the test database. // Do NOT import fixtures in a production database! // Existing data would be deleted. + + // Set default fixtures if none where specified + if len(fixtureFiles) == 0 { + fixtureFiles = []string{ + "testdata/fixtures/calls.yml", + "testdata/fixtures/invitations.yml", + "testdata/fixtures/persons.yml", + } + } + fixtures, err = testfixtures.New( - testfixtures.Database(db.DB), // You database connection - testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" - testfixtures.Files("./testdata/fixtures/persons.yml"), // the directory containing the YAML files - testfixtures.Files("./testdata/fixtures/invitations.yml"), // the directory containing the YAML files - testfixtures.Files("./testdata/fixtures/calls.yml"), // the directory containing the YAML files + testfixtures.Database(db.DB), // You database connection + testfixtures.Dialect("sqlite"), // Available: "postgresql", "timescaledb", "mysql", "mariadb", "sqlite" and "sqlserver" + testfixtures.Files(fixtureFiles...), // the directory containing the YAML files ) if err != nil { panic(err) @@ -381,7 +455,13 @@ func TestBridge_GetCallStatus(t *testing.T) { func TestBridge_GetActiveCalls(t *testing.T) { - prepareTestDatabase() + prepareTestDatabase("testdata/fixtures/TestBridge_GetActiveCalls/calls.yml") + + calls, err := bridge.GetAllCalls() + + if err != nil { + panic(err) + } tests := []struct { name string @@ -389,11 +469,8 @@ func TestBridge_GetActiveCalls(t *testing.T) { wantErr bool }{ { - name: "Get two active calls", - want: []Call{ - fixtureCalls[0], - fixtureCalls[1], - }, + name: "Get two active calls", + want: []Call{calls[0], calls[1]}, wantErr: false, }, } @@ -577,7 +654,12 @@ func TestNewBridge(t *testing.T) { func TestBridge_DeleteOldCalls(t *testing.T) { - prepareTestDatabase() + prepareTestDatabase("testdata/fixtures/TestBridge_DeleteOldCalls/calls.yml") + + calls, err := bridge.GetAllCalls() + if err != nil { + panic(err) + } tests := []struct { name string @@ -585,10 +667,7 @@ func TestBridge_DeleteOldCalls(t *testing.T) { }{ { name: "Get all calls after running deletion", - want: []Call{ - fixtureCalls[0], - fixtureCalls[1], - }, + want: []Call{calls[2]}, }, } for _, tt := range tests { @@ -680,7 +759,10 @@ func TestBridge_CallFull(t *testing.T) { } func TestBridge_LastCallNotified(t *testing.T) { - prepareTestDatabase() + + prepareTestDatabase( + "testdata/fixtures/TestBridge_LastCallNotified/invitations.yml", + "testdata/fixtures/TestBridge_LastCallNotified/calls.yml") tests := []struct { name string @@ -688,9 +770,11 @@ func TestBridge_LastCallNotified(t *testing.T) { want Call wantErr bool }{ - {"Phone 1230", fixturePersons[0], fixtureCalls[0], false}, - {"Phone 1231", fixturePersons[1], fixtureCalls[0], false}, - {"Phone 1232", fixturePersons[2], fixtureCalls[1], false}, + {"Phone 0", Person{Phone: "0"}, fixtureCalls[1], false}, + {"Phone 1", Person{Phone: "1"}, fixtureCalls[2], false}, + {"Phone 2", Person{Phone: "2"}, fixtureCalls[1], false}, + {"Phone 3", Person{Phone: "3"}, fixtureCalls[1], false}, + {"Phone 4", Person{Phone: "4"}, fixtureCalls[1], false}, {"Phone noexist", Person{Phone: "noexist"}, Call{}, true}, } for _, tt := range tests { @@ -709,30 +793,63 @@ func TestBridge_LastCallNotified(t *testing.T) { } func TestBridge_PersonAcceptLastCall(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - phoneNumber string + + prepareTestDatabase( + "testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml", + "testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml", + ) + + gotBefore, err := bridge.GetInvitations() + + if err != nil { + panic(err) } + tests := []struct { - name string - fields fields - args args - wantErr bool + name string + phoneNumber string + want []Invitation + wantErr bool }{ - // TODO: Add test cases. + {"Phone 1230", "1230", gotBefore, false}, + {"Phone 1231", "1231", []Invitation{ + { + ID: 1, + Phone: "1231", + CallID: 1, + Status: InvitationAccepted, + Time: time.Now(), + }, + gotBefore[0], + gotBefore[3], + gotBefore[2], + gotBefore[4], + }, false}, + {"Phone 1232", "1232", gotBefore, true}, + {"Phone noexist", "noexist", gotBefore, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - if err := b.PersonAcceptLastCall(tt.args.phoneNumber); (err != nil) != tt.wantErr { + + // Reset database to initial values on each test, since the tests + // change the contents + prepareTestDatabase( + "testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml", + "testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml", + ) + + if err := bridge.PersonAcceptLastCall(tt.phoneNumber); (err != nil) != tt.wantErr { t.Errorf("Bridge.PersonAcceptLastCall() error = %v, wantErr %v", err, tt.wantErr) } + + gotAfter, err := bridge.GetInvitations() + if err != nil { + panic(err) + } + + if diff := cmp.Diff(tt.want, gotAfter); diff != "" { + t.Errorf("Bridge.GetInvitations() after PersonAcceptLastCall(%s) mismatch (-want +got):\n%s", tt.phoneNumber, diff) + } }) } } @@ -769,6 +886,7 @@ func TestBridge_PersonCancelCall(t *testing.T) { func TestBridge_PersonDelete(t *testing.T) { prepareTestDatabase() + tests := []struct { name string phoneNumber string @@ -861,6 +979,9 @@ func TestBridge_GetAllCalls(t *testing.T) { } func TestBridge_GetInvitations(t *testing.T) { + + prepareTestDatabase() + tests := []struct { name string want []Invitation diff --git a/go.sum b/go.sum index a8530a3..c8b3df1 100644 --- a/go.sum +++ b/go.sum @@ -166,6 +166,7 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= diff --git a/helper.go b/helper.go index 485361a..d861185 100644 --- a/helper.go +++ b/helper.go @@ -45,9 +45,10 @@ func contextString(key contextKey, r *http.Request) string { // genOTP generates a OTP to verify the person on-site. The OTP is the first 5 // chars of the SHA-1 hash of phonenumber+callID+tokenSecret func genOTP(phone string, callID int) string { + h := sha1.New() // Firt value are written bytes, we only care about the error if any - if _, err := h.Write([]byte(phone + strconv.Itoa(callID) + tokenSecret)); err != nil { + if _, err := h.Write([]byte(phone + strconv.Itoa(callID) + os.Getenv("IMPF_TOKEN_SECRET"))); err != nil { log.Error(err) } return hex.EncodeToString(h.Sum(nil))[1:5] diff --git a/main.go b/main.go index 8458b48..36a3200 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,15 @@ import ( "github.com/gorilla/sessions" ) +// HTTPClient interface +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +var ( + Client HTTPClient +) + var ( // Instance of the main application @@ -24,11 +33,9 @@ var ( store *sessions.CookieStore // API auth for twilio - apiUser string - apiPass string - tokenSecret string - disableSMS string - dbPath string + // apiUser string + // apiPass string + // dbPath string ) // User holds a users account information @@ -38,6 +45,7 @@ type User struct { } func init() { + Client = &http.Client{} store = sessions.NewCookieStore([]byte(os.Getenv("IMPF_SESSION_SECRET"))) @@ -57,15 +65,9 @@ func init() { } // Show more logs if IMPF_MODE=DEVEL is set - apiUser = os.Getenv("IMPF_TWILIO_USER") - apiPass = os.Getenv("IMPF_TWILIO_PASS") - tokenSecret = os.Getenv("IMPF_TOKEN_SECRET") - disableSMS = os.Getenv("IMPF_DISABLE_SMS") - dbPath = os.Getenv("IMPF_DB_FILE") - - // Add default if not set - if dbPath == "" { - dbPath = "./data.db" + if os.Getenv("IMPF_DB_FILE") == "" { + // Add default if not set + os.Setenv("IMPF_DB_FILE", "./data.db") } // Intial setup. Instanciate bridge and parse html templates @@ -117,6 +119,10 @@ func main() { } func middlewareAPI(h http.Handler) http.Handler { + + apiUser := os.Getenv("IMPF_TWILIO_USER") + apiPass := os.Getenv("IMPF_TWILIO_PASS") + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user, pass, ok := r.BasicAuth() diff --git a/sender.go b/sender.go index 6471c5d..74ea285 100644 --- a/sender.go +++ b/sender.go @@ -1,6 +1,7 @@ package main import ( + "os" // "bytes" "encoding/json" "fmt" @@ -40,8 +41,12 @@ func (s TwillioSender) SendMessage(msgTo, msgBody string) error { data.Set("Parameters", string(jsonData)) + if os.Getenv("IMPF_DISABLE_SMS") != "" { + log.Info("SMS sending disabled. Unset IMPF_DISABLE_SMS to enable") + return nil + } + // Create a new client and request - client := &http.Client{} r, err := http.NewRequest("POST", s.endpoint, strings.NewReader(data.Encode())) // URL-encoded payload if err != nil { return err @@ -51,13 +56,8 @@ func (s TwillioSender) SendMessage(msgTo, msgBody string) error { r.Header.Add("Content-Type", "application/x-www-form-urlencoded") r.SetBasicAuth(s.user, s.token) - if disableSMS != "" { - log.Info("SMS sending disabled. Unset IMPF_DISABLE_SMS to enable") - return nil - } - // Execute the request - res, err := client.Do(r) + res, err := Client.Do(r) if err != nil { return err } diff --git a/testdata/fixtures/TestBridge_DeleteOldCalls/calls.yml b/testdata/fixtures/TestBridge_DeleteOldCalls/calls.yml new file mode 100644 index 0000000..a45b14a --- /dev/null +++ b/testdata/fixtures/TestBridge_DeleteOldCalls/calls.yml @@ -0,0 +1,44 @@ +- id: 1 + title: "Call number 1" + center_id: 0 + capacity: 1 + time_start: "2021-02-10 12:30:00+01:00" + time_end: "2021-02-10 12:35:00+01:00" + loc_name: "loc_name1" + age_min: 0 + age_max: 100 + loc_street: "loc_street1" + loc_housenr: "loc_housenr1" + loc_plz: "loc_plz1" + loc_city: "loc_city1" + loc_opt: "loc_opt1" + +- id: 2 + title: "Call number 2" + center_id: 0 + capacity: 2 + time_start: "2021-02-10 12:31:00+01:00" + time_end: "2021-02-10 12:36:00+01:00" + age_min: 0 + age_max: 70 + loc_name: "loc_name2" + loc_street: "loc_street2" + loc_housenr: "loc_housenr2" + loc_plz: "loc_plz2" + loc_city: "loc_city2" + loc_opt: "" + +- id: 3 + title: "Call number 3" + center_id: 0 + capacity: 3 + time_start: "2021-01-01 12:30:00+01:00" + time_end: "3000-01-01 12:35:00+01:00" + age_min: 70 + age_max: 200 + loc_name: "loc_name3" + loc_street: "loc_street3" + loc_housenr: "loc_housenr3" + loc_plz: "loc_plz3" + loc_city: "loc_city3" + loc_opt: "loc_opt3" diff --git a/testdata/fixtures/TestBridge_GetActiveCalls/calls.yml b/testdata/fixtures/TestBridge_GetActiveCalls/calls.yml new file mode 100644 index 0000000..a0f589e --- /dev/null +++ b/testdata/fixtures/TestBridge_GetActiveCalls/calls.yml @@ -0,0 +1,44 @@ +- id: 1 + title: "Call number 1" + center_id: 0 + capacity: 1 + time_start: "2021-02-10 12:30:00+01:00" + time_end: "3000-02-10 12:35:00+01:00" + loc_name: "loc_name1" + age_min: 0 + age_max: 100 + loc_street: "loc_street1" + loc_housenr: "loc_housenr1" + loc_plz: "loc_plz1" + loc_city: "loc_city1" + loc_opt: "loc_opt1" + +- id: 2 + title: "Call number 2" + center_id: 0 + capacity: 2 + time_start: "2021-02-10 12:31:00+01:00" + time_end: "3000-02-10 12:36:00+01:00" + age_min: 0 + age_max: 70 + loc_name: "loc_name2" + loc_street: "loc_street2" + loc_housenr: "loc_housenr2" + loc_plz: "loc_plz2" + loc_city: "loc_city2" + loc_opt: "" + +- id: 3 + title: "Call number 3" + center_id: 0 + capacity: 3 + time_start: "2021-01-01 12:30:00+01:00" + time_end: "2021-01-01 12:35:00+01:00" + age_min: 70 + age_max: 200 + loc_name: "loc_name3" + loc_street: "loc_street3" + loc_housenr: "loc_housenr3" + loc_plz: "loc_plz3" + loc_city: "loc_city3" + loc_opt: "loc_opt3" diff --git a/testdata/fixtures/calls_selectnext.yml b/testdata/fixtures/TestBridge_LastCallNotified/calls.yml similarity index 95% rename from testdata/fixtures/calls_selectnext.yml rename to testdata/fixtures/TestBridge_LastCallNotified/calls.yml index 466f5ca..ac46cc9 100644 --- a/testdata/fixtures/calls_selectnext.yml +++ b/testdata/fixtures/TestBridge_LastCallNotified/calls.yml @@ -6,7 +6,7 @@ time_end: "2021-02-10 12:35:00+01:00" loc_name: "loc_name1" age_min: 0 - age_max: 65 + age_max: 100 loc_street: "loc_street1" loc_housenr: "loc_housenr1" loc_plz: "loc_plz1" @@ -20,7 +20,7 @@ time_start: "2021-02-10 12:31:00+01:00" time_end: "2021-02-10 12:36:00+01:00" age_min: 0 - age_max: 100 + age_max: 70 loc_name: "loc_name2" loc_street: "loc_street2" loc_housenr: "loc_housenr2" @@ -34,8 +34,8 @@ capacity: 3 time_start: "2021-01-01 12:30:00+01:00" time_end: "2021-01-01 12:35:00+01:00" - age_min: 60 - age_max: 100 + age_min: 70 + age_max: 200 loc_name: "loc_name3" loc_street: "loc_street3" loc_housenr: "loc_housenr3" diff --git a/testdata/fixtures/TestBridge_LastCallNotified/invitations.yml b/testdata/fixtures/TestBridge_LastCallNotified/invitations.yml new file mode 100644 index 0000000..3a1a50e --- /dev/null +++ b/testdata/fixtures/TestBridge_LastCallNotified/invitations.yml @@ -0,0 +1,42 @@ +- id: 0 + phone: "0" + call_id: 1 + status: "notified" + time: "2021-02-10 12:00:00+01:00" + +- id: 1 + phone: "0" + call_id: 2 + status: "accepted" + time: "2021-02-10 12:01:00+01:00" + +- id: 2 + phone: "1" + call_id: 2 + status: "notified" + time: "2021-02-10 12:00:00+01:00" + +- id: 3 + phone: "1" + call_id: 3 + status: "notified" + time: "2021-02-10 12:05:00+01:00" + +- id: 4 + phone: "2" + call_id: 2 + status: "notified" + time: "2021-02-10 12:05:00+01:00" + +- id: 5 + phone: "3" + call_id: 2 + status: "rejected" + time: "2021-02-10 12:05:00+01:00" + +- id: 6 + phone: "4" + call_id: 2 + status: "accepted" + time: "2021-02-10 12:05:00+01:00" + diff --git a/testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml b/testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml new file mode 100644 index 0000000..e63cf30 --- /dev/null +++ b/testdata/fixtures/TestBridge_PersonAcceptLastCall/calls.yml @@ -0,0 +1,46 @@ + +- id: 1 + title: "Call number 1" + center_id: 0 + capacity: 5 + time_start: "2021-02-10 12:30:00+01:00" + time_end: "2021-02-10 12:35:00+01:00" + loc_name: "loc_name1" + age_min: 0 + age_max: 100 + loc_street: "loc_street1" + loc_housenr: "loc_housenr1" + loc_plz: "loc_plz1" + loc_city: "loc_city1" + loc_opt: "loc_opt1" + +- id: 2 + title: "Call number 2" + center_id: 0 + capacity: 5 + time_start: "2021-02-10 12:31:00+01:00" + time_end: "2021-02-10 12:36:00+01:00" + age_min: 0 + age_max: 70 + loc_name: "loc_name2" + loc_street: "loc_street2" + loc_housenr: "loc_housenr2" + loc_plz: "loc_plz2" + loc_city: "loc_city2" + loc_opt: "" + +- id: 3 + title: "Call number 3" + center_id: 0 + capacity: 5 + time_start: "2021-01-01 12:30:00+01:00" + time_end: "2021-01-01 12:35:00+01:00" + age_min: 70 + age_max: 200 + loc_name: "loc_name3" + loc_street: "loc_street3" + loc_housenr: "loc_housenr3" + loc_plz: "loc_plz3" + loc_city: "loc_city3" + loc_opt: "loc_opt3" + diff --git a/testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml b/testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml new file mode 100644 index 0000000..c7a80b3 --- /dev/null +++ b/testdata/fixtures/TestBridge_PersonAcceptLastCall/invitations.yml @@ -0,0 +1,29 @@ +- id: 0 + phone: "1230" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 1 + phone: "1231" + call_id: 1 + status: "notified" + time: "2021-02-10 12:39:00+01:00" + +- id: 2 + phone: "1231" + call_id: 2 + status: "notified" + time: "2021-02-10 12:37:00+01:00" + +- id: 4 + phone: "1231" + call_id: 3 + status: "notified" + time: "2021-02-10 12:38:00+01:00" + +- id: 3 + phone: "1232" + call_id: 2 + status: "rejected" + time: "2021-02-10 12:40:00+01:00" diff --git a/testdata/fixtures/invitations_selectnext.yml b/testdata/fixtures/invitations_selectnext.yml deleted file mode 100644 index 7883fd6..0000000 --- a/testdata/fixtures/invitations_selectnext.yml +++ /dev/null @@ -1,17 +0,0 @@ -- id: 0 - phone: "1230" - call_id: 1 - status: "accepted" - time: "2021-02-10 12:36:00+01:00" - -- id: 1 - phone: "1231" - call_id: 1 - status: "accepted" - time: "2021-02-10 12:36:00+01:00" - -- id: 2 - phone: "1232" - call_id: 2 - status: "rejected" - time: "2021-02-10 12:36:00+01:00" diff --git a/testdata/fixtures/persons_selectnext.yml b/testdata/fixtures/persons_selectnext.yml deleted file mode 100644 index 9705e89..0000000 --- a/testdata/fixtures/persons_selectnext.yml +++ /dev/null @@ -1,65 +0,0 @@ -- phone: "0" - center_id: 0 - age: 1 - group_num: 1 - status: 0 - -- phone: "10" - center_id: 0 - age: 20 - group_num: 1 - status: 1 - -- phone: "11" - center_id: 0 - age: 40 - group_num: 1 - status: 0 - -- phone: "1" - center_id: 0 - age: 60 - group_num: 1 - status: 0 - -- phone: "2" - center_id: 0 - age: 65 - group_num: 2 - status: 0 - -- phone: "3" - center_id: 0 - age: 70 - group_num: 2 - status: 0 - -- phone: "4" - center_id: 0 - age: 75 - group_num: 4 - status: 0 - -- phone: "7" - center_id: 0 - age: 80 - group_num: 4 - status: 0 - -- phone: "8" - center_id: 0 - age: 85 - group_num: 4 - status: 0 - -- phone: "5" - center_id: 0 - age: 90 - group_num: 4 - status: 0 - -- phone: "6" - center_id: 1 - age: 100 - group_num: 2 - status: 0 From da22dc6700411805157bffb44767a1439b4c8565 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 21:52:03 +0100 Subject: [PATCH 13/15] add test for Acceptlastcall() --- bridge_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bridge_test.go b/bridge_test.go index 988a492..a377975 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -821,11 +821,11 @@ func TestBridge_PersonAcceptLastCall(t *testing.T) { Time: time.Now(), }, gotBefore[0], - gotBefore[3], gotBefore[2], + gotBefore[3], gotBefore[4], }, false}, - {"Phone 1232", "1232", gotBefore, true}, + {"Phone 1232", "1232", gotBefore, false}, {"Phone noexist", "noexist", gotBefore, true}, } for _, tt := range tests { From 6c003bf8c60ea75d885d38c565a66cd3a2fc23e3 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 22:28:03 +0100 Subject: [PATCH 14/15] add test for personCancellAllCalls() --- bridge.go | 12 +- bridge_test.go | 167 ++++++++++++++---- handler_api.go | 2 +- .../invitations.yml | 48 +++++ 4 files changed, 194 insertions(+), 35 deletions(-) create mode 100644 testdata/fixtures/TestBridge_PersonCancelAllCalls/invitations.yml diff --git a/bridge.go b/bridge.go index 1dafc26..d68db8a 100644 --- a/bridge.go +++ b/bridge.go @@ -607,15 +607,19 @@ func (b *Bridge) PersonAcceptLastCall(phoneNumber string) error { return err } -// PersonCancelCall cancels the last call a person was invited to -func (b *Bridge) PersonCancelCall(phoneNumber string) error { +// PersonCancelAllCalls cancels all accepted calls +func (b *Bridge) PersonCancelAllCalls(phoneNumber string) error { log.Debugf("Cancelling call for number %s\n", phoneNumber) _, err := bridge.db.NamedExec( - `UPDATE invitations SET status = "cancelled" WHERE phone=:phone`, + `UPDATE invitations SET status=:newstatus, time=:time WHERE phone=:phone AND status=:oldstatus`, + map[string]interface{}{ - "phone": phoneNumber, + "phone": phoneNumber, + "oldstatus": InvitationAccepted, + "newstatus": InvitationCancelled, + "time": time.Now(), }, ) diff --git a/bridge_test.go b/bridge_test.go index a377975..45b2195 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" "io/ioutil" + "net/http" "os" "reflect" "strconv" @@ -16,7 +17,6 @@ import ( "github.com/google/go-cmp/cmp" "github.com/jmoiron/sqlx" _ "github.com/mattn/go-sqlite3" - "net/http" ) // Custom type that allows setting the func that our Mock Do func will run @@ -35,6 +35,8 @@ var ( sender *TwillioSender loc = time.FixedZone("+0100", 3600) + fakeNow time.Time = time.Date(2999, 1, 1, 20, 0, 0, 0, time.UTC) + fixtureCalls []Call = []Call{ { ID: 1, @@ -854,35 +856,6 @@ func TestBridge_PersonAcceptLastCall(t *testing.T) { } } -func TestBridge_PersonCancelCall(t *testing.T) { - type fields struct { - db *sqlx.DB - sender *TwillioSender - } - type args struct { - phoneNumber string - } - tests := []struct { - name string - fields fields - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &Bridge{ - db: tt.fields.db, - sender: tt.fields.sender, - } - if err := b.PersonCancelCall(tt.args.phoneNumber); (err != nil) != tt.wantErr { - t.Errorf("Bridge.PersonCancelCall() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - func TestBridge_PersonDelete(t *testing.T) { prepareTestDatabase() @@ -1003,3 +976,137 @@ func TestBridge_GetInvitations(t *testing.T) { }) } } + +func TestBridge_PersonCancelAllCalls(t *testing.T) { + prepareTestDatabase("testdata/fixtures/TestBridge_PersonCancelAllCalls/invitations.yml") + + gotBefore, err := bridge.GetInvitations() + if err != nil { + panic(err) + } + + tests := []struct { + name string + phoneNumber string + want []Invitation + wantErr bool + }{ + { + "Cancel Calls of phone 1230", + "1230", + []Invitation{ + { + ID: 0, + Phone: "1230", + CallID: 1, + Status: "cancelled", + Time: fakeNow, + }, + { + ID: 1, + Phone: "1230", + CallID: 2, + Status: "cancelled", + Time: fakeNow, + }, + gotBefore[2], + gotBefore[3], + gotBefore[4], + gotBefore[5], + gotBefore[6], + gotBefore[7], + }, + false}, + { + "Cancel Calls of phone 1231", + "1231", + []Invitation{ + { + ID: 0, + Phone: "1230", + CallID: 1, + Status: "cancelled", + Time: fakeNow, + }, + { + ID: 1, + Phone: "1230", + CallID: 2, + Status: "cancelled", + Time: fakeNow, + }, + + { + ID: 4, + Phone: "1231", + CallID: 1, + Status: "cancelled", + Time: fakeNow, + }, + { + ID: 5, + Phone: "1231", + CallID: 2, + Status: "cancelled", + Time: fakeNow, + }, + gotBefore[2], + gotBefore[3], + gotBefore[6], + gotBefore[7], + }, + false}, + {"Cancel Calls of phone noexist", "noexist", []Invitation{ + { + ID: 0, + Phone: "1230", + CallID: 1, + Status: "cancelled", + Time: fakeNow, + }, + { + ID: 1, + Phone: "1230", + CallID: 2, + Status: "cancelled", + Time: fakeNow, + }, + + { + ID: 4, + Phone: "1231", + CallID: 1, + Status: "cancelled", + Time: fakeNow, + }, + { + ID: 5, + Phone: "1231", + CallID: 2, + Status: "cancelled", + Time: fakeNow, + }, + gotBefore[2], + gotBefore[3], + gotBefore[6], + gotBefore[7], + }, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := bridge.PersonCancelAllCalls(tt.phoneNumber); (err != nil) != tt.wantErr { + t.Errorf("Bridge.PersonCancelAllCalls() error = %v, wantErr %v", err, tt.wantErr) + } + + got, err := bridge.GetInvitations() + if err != nil { + panic(err) + } + + if diff := cmp.Diff(tt.want, got); diff != "" { + t.Errorf("Bridge.GetInvitations() mismatch (-want +got):\n%s", diff) + } + + }) + } +} diff --git a/handler_api.go b/handler_api.go index b621cd3..ba54871 100644 --- a/handler_api.go +++ b/handler_api.go @@ -40,7 +40,7 @@ func handlerAPI(w http.ResponseWriter, r *http.Request) { header = http.StatusBadRequest } case "storno": - if err := bridge.PersonCancelCall(phoneNumber); err != nil { + if err := bridge.PersonCancelAllCalls(phoneNumber); err != nil { log.Error(err) header = http.StatusBadRequest } diff --git a/testdata/fixtures/TestBridge_PersonCancelAllCalls/invitations.yml b/testdata/fixtures/TestBridge_PersonCancelAllCalls/invitations.yml new file mode 100644 index 0000000..f8c7224 --- /dev/null +++ b/testdata/fixtures/TestBridge_PersonCancelAllCalls/invitations.yml @@ -0,0 +1,48 @@ +- id: 0 + phone: "1230" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 1 + phone: "1230" + call_id: 2 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 2 + phone: "1230" + call_id: 4 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" + +- id: 3 + phone: "1230" + call_id: 3 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" + + +- id: 4 + phone: "1231" + call_id: 1 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 5 + phone: "1231" + call_id: 2 + status: "accepted" + time: "2021-02-10 12:36:00+01:00" + +- id: 6 + phone: "1231" + call_id: 4 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" + +- id: 7 + phone: "1231" + call_id: 3 + status: "rejected" + time: "2021-02-10 12:36:00+01:00" From 0c1f66f88e1425053e82af315f999aeefd3aac49 Mon Sep 17 00:00:00 2001 From: Pablo Ovelleiro Corral Date: Mon, 15 Feb 2021 22:35:08 +0100 Subject: [PATCH 15/15] set status=rejected for invitations --- bridge.go | 28 +++++++++++++++++++++++++--- bridge_test.go | 4 +++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/bridge.go b/bridge.go index d68db8a..d2896aa 100644 --- a/bridge.go +++ b/bridge.go @@ -472,10 +472,10 @@ type Invitation struct { type invitationStatus string const ( + InvitationRejected invitationStatus = "rejected" InvitationAccepted invitationStatus = "accepted" - InvitationRejected = "rejected" - InvitationCancelled = "cancelled" - InvitationNotified = "notified" + InvitationCancelled invitationStatus = "cancelled" + InvitationNotified invitationStatus = "notified" ) func (b *Bridge) GetInvitations() ([]Invitation, error) { @@ -538,6 +538,28 @@ func (b *Bridge) PersonAcceptLastCall(phoneNumber string) error { if isFull { log.Debugf("Rejecting number %s for call (is full)\n", phoneNumber) + _, err = bridge.db.NamedExec( + `UPDATE invitations SET + status=:status, + time=:time + WHERE + phone=:phone + AND call_id=:call_id + AND status=:oldstatus`, + map[string]interface{}{ + "status": InvitationAccepted, + "oldstatus": InvitationRejected, + "phone": phoneNumber, + "time": time.Now(), + "call_id": lastCall.ID, + }, + ) + + if err != nil { + log.Errorf("Failed to set accepted status for last invitation of %s\n", phoneNumber) + return err + } + if err := b.sender.SendMessageReject(phoneNumber); err != nil { log.Errorf("Failed to send reject message for phone %s\n", phoneNumber) log.Error(err) diff --git a/bridge_test.go b/bridge_test.go index 45b2195..d743fcc 100644 --- a/bridge_test.go +++ b/bridge_test.go @@ -149,7 +149,9 @@ func formatRequest(r *http.Request) string { // If this is a POST, add post data if r.Method == "POST" { - r.ParseForm() + if err := r.ParseForm(); err != nil { + panic(err) + } // request = append(request, "Formdata:") request = append(request, r.Form.Encode()) } // Return the request as a string