scouting: Add an endpoint for populating the match schedule

This patch combines the scraping library with the scouting webserver.
There's now also a new end point for the web page (or debug CLI tool)
to ask the server to fetch the match list. The end point is
`/requests/refresh_match_list`.

All the tests are updated. The `cli_test` downloads a 2016 ny_tr match
list that I downloaded from TBA. It should be a decent integration
test as it uses representative data.

Signed-off-by: Philipp Schrader <philipp.schrader@gmail.com>
Change-Id: I6c540590521b00887eb2ddde2a9369875c659551
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index e3650ff..999e955 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -8,8 +8,11 @@
 	"testing"
 
 	"github.com/frc971/971-Robot-Code/scouting/db"
+	"github.com/frc971/971-Robot-Code/scouting/scraping"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/debug"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
@@ -26,7 +29,7 @@
 func Test404(t *testing.T) {
 	db := MockDatabase{}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -43,7 +46,7 @@
 func TestSubmitDataScoutingError(t *testing.T) {
 	db := MockDatabase{}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -71,7 +74,7 @@
 func TestSubmitDataScouting(t *testing.T) {
 	db := MockDatabase{}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -119,7 +122,7 @@
 		},
 	}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -174,7 +177,7 @@
 		},
 	}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -227,7 +230,7 @@
 		},
 	}
 	scoutingServer := server.NewScoutingServer()
-	HandleRequests(&db, scoutingServer)
+	HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
 	scoutingServer.Start(8080)
 	defer scoutingServer.Stop()
 
@@ -269,6 +272,79 @@
 	}
 }
 
+// Validates that we can download the schedule from The Blue Alliance.
+func TestRefreshMatchList(t *testing.T) {
+	scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
+		return []scraping.Match{
+			{
+				CompLevel:   "qual",
+				MatchNumber: 1,
+				Alliances: scraping.Alliances{
+					Red: scraping.Alliance{
+						TeamKeys: []string{
+							"100",
+							"200",
+							"300",
+						},
+					},
+					Blue: scraping.Alliance{
+						TeamKeys: []string{
+							"101",
+							"201",
+							"301",
+						},
+					},
+				},
+				WinningAlliance: "",
+				EventKey:        "",
+				Time:            0,
+				PredictedTime:   0,
+				ActualTime:      0,
+				PostResultTime:  0,
+				ScoreBreakdowns: scraping.ScoreBreakdowns{},
+			},
+		}, nil
+	}
+
+	database := MockDatabase{}
+	scoutingServer := server.NewScoutingServer()
+	HandleRequests(&database, scrapeMockSchedule, scoutingServer)
+	scoutingServer.Start(8080)
+	defer scoutingServer.Stop()
+
+	builder := flatbuffers.NewBuilder(1024)
+	builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
+
+	response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
+	if err != nil {
+		t.Fatal("Failed to request all matches: ", err)
+	}
+
+	// Validate the response.
+	expected := refresh_match_list_response.RefreshMatchListResponseT{}
+	if !reflect.DeepEqual(expected, *response) {
+		t.Fatal("Expected ", expected, ", but got ", *response)
+	}
+
+	// Make sure that the data made it into the database.
+	expectedMatches := []db.Match{
+		{
+			MatchNumber: 1,
+			Round:       1,
+			CompLevel:   "qual",
+			R1:          100,
+			R2:          200,
+			R3:          300,
+			B1:          101,
+			B2:          201,
+			B3:          301,
+		},
+	}
+	if !reflect.DeepEqual(expectedMatches, database.matches) {
+		t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
+	}
+}
+
 // A mocked database we can use for testing. Add functionality to this as
 // needed for your tests.
 
@@ -277,7 +353,8 @@
 	stats   []db.Stats
 }
 
-func (database *MockDatabase) AddToMatch(db.Match) error {
+func (database *MockDatabase) AddToMatch(match db.Match) error {
+	database.matches = append(database.matches, match)
 	return nil
 }
 
@@ -309,3 +386,8 @@
 func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
 	return []db.Stats{}, nil
 }
+
+// Returns an empty match list from the fake The Blue Alliance scraping.
+func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
+	return nil, nil
+}