Scouting: add function to request 2023 stats

Signed-off-by: Emily Markova <emily.markova@gmail.com>
Change-Id: I5b5763f89ea1f6a4876be5af20553756f303b919
diff --git a/scouting/db/db_test.go b/scouting/db/db_test.go
index 477a0f1..d775404 100644
--- a/scouting/db/db_test.go
+++ b/scouting/db/db_test.go
@@ -233,6 +233,94 @@
 	}
 }
 
+func TestAddToStats2023DB(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	correct := []Stats2023{
+		Stats2023{
+			TeamNumber: "6344", MatchNumber: 3, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 0,
+			MiddleCubesAuto: 1, HighCubesAuto: 0, CubesDroppedAuto: 1,
+			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 2,
+			ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 2,
+			HighCubes: 1, CubesDropped: 0, LowCones: 0,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			AvgCycle: 0, CollectedBy: "emma",
+		},
+		Stats2023{
+			TeamNumber: "7454", MatchNumber: 3, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 2, LowCubesAuto: 1,
+			MiddleCubesAuto: 2, HighCubesAuto: 2, CubesDroppedAuto: 0,
+			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
+			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 0,
+			HighCubes: 0, CubesDropped: 1, LowCones: 0,
+			MiddleCones: 0, HighCones: 1, ConesDropped: 0,
+			AvgCycle: 0, CollectedBy: "tyler",
+		},
+		Stats2023{
+			TeamNumber: "4354", MatchNumber: 3, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 0,
+			MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
+			LowConesAuto: 0, MiddleConesAuto: 2, HighConesAuto: 1,
+			ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
+			HighCubes: 2, CubesDropped: 1, LowCones: 1,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 1,
+			AvgCycle: 0, CollectedBy: "isaac",
+		},
+		Stats2023{
+			TeamNumber: "6533", MatchNumber: 3, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 0,
+			MiddleCubesAuto: 2, HighCubesAuto: 1, CubesDroppedAuto: 1,
+			LowConesAuto: 1, MiddleConesAuto: 0, HighConesAuto: 0,
+			ConesDroppedAuto: 0, LowCubes: 0, MiddleCubes: 1,
+			HighCubes: 2, CubesDropped: 1, LowCones: 0,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 0,
+			AvgCycle: 0, CollectedBy: "will",
+		},
+		Stats2023{
+			TeamNumber: "8354", MatchNumber: 3, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 2, LowCubesAuto: 1,
+			MiddleCubesAuto: 1, HighCubesAuto: 2, CubesDroppedAuto: 0,
+			LowConesAuto: 0, MiddleConesAuto: 1, HighConesAuto: 1,
+			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 0,
+			HighCubes: 0, CubesDropped: 2, LowCones: 1,
+			MiddleCones: 1, HighCones: 0, ConesDropped: 1,
+			AvgCycle: 0, CollectedBy: "unkown",
+		},
+	}
+
+	matches := []TeamMatch{
+		TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 1, TeamNumber: 6344},
+		TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 2, TeamNumber: 7454},
+		TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 3, TeamNumber: 4354},
+		TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
+			Alliance: "B", AlliancePosition: 1, TeamNumber: 6533},
+		TeamMatch{MatchNumber: 3, SetNumber: 1, CompLevel: "quals",
+			Alliance: "B", AlliancePosition: 2, TeamNumber: 8354},
+	}
+
+	for _, match := range matches {
+		err := fixture.db.AddToMatch(match)
+		check(t, err, "Failed to add match")
+	}
+
+	for i := 0; i < len(correct); i++ {
+		err := fixture.db.AddToStats2023(correct[i])
+		check(t, err, "Failed to add 2023stats to DB")
+	}
+
+	got, err := fixture.db.ReturnStats2023()
+	check(t, err, "Failed ReturnStats2023()")
+
+	if !reflect.DeepEqual(correct, got) {
+		t.Errorf("Got %#v,\nbut expected %#v.", got, correct)
+	}
+}
+
 func TestAddDuplicateStats(t *testing.T) {
 	fixture := createDatabase(t)
 	defer fixture.TearDown()
@@ -808,6 +896,82 @@
 	}
 }
 
+func TestReturnStats2023DB(t *testing.T) {
+	fixture := createDatabase(t)
+	defer fixture.TearDown()
+
+	correct := []Stats2023{
+		Stats2023{
+			TeamNumber: "2343", MatchNumber: 2, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 1,
+			MiddleCubesAuto: 2, HighCubesAuto: 2, CubesDroppedAuto: 1,
+			LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 2,
+			ConesDroppedAuto: 1, LowCubes: 1, MiddleCubes: 2,
+			HighCubes: 1, CubesDropped: 0, LowCones: 2,
+			MiddleCones: 0, HighCones: 2, ConesDropped: 1,
+			AvgCycle: 51, CollectedBy: "isaac",
+		},
+		Stats2023{
+			TeamNumber: "5443", MatchNumber: 2, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 2, LowCubesAuto: 1,
+			MiddleCubesAuto: 1, HighCubesAuto: 0, CubesDroppedAuto: 1,
+			LowConesAuto: 1, MiddleConesAuto: 1, HighConesAuto: 0,
+			ConesDroppedAuto: 0, LowCubes: 2, MiddleCubes: 2,
+			HighCubes: 1, CubesDropped: 0, LowCones: 1,
+			MiddleCones: 0, HighCones: 2, ConesDropped: 1,
+			AvgCycle: 39, CollectedBy: "jack",
+		},
+		Stats2023{
+			TeamNumber: "5436", MatchNumber: 2, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 0,
+			MiddleCubesAuto: 2, HighCubesAuto: 0, CubesDroppedAuto: 1,
+			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
+			ConesDroppedAuto: 1, LowCubes: 2, MiddleCubes: 2,
+			HighCubes: 0, CubesDropped: 0, LowCones: 1,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			AvgCycle: 45, CollectedBy: "martin",
+		},
+		Stats2023{
+			TeamNumber: "5643", MatchNumber: 2, SetNumber: 1,
+			CompLevel: "quals", StartingQuadrant: 4, LowCubesAuto: 0,
+			MiddleCubesAuto: 0, HighCubesAuto: 1, CubesDroppedAuto: 1,
+			LowConesAuto: 2, MiddleConesAuto: 0, HighConesAuto: 0,
+			ConesDroppedAuto: 1, LowCubes: 2, MiddleCubes: 2,
+			HighCubes: 0, CubesDropped: 0, LowCones: 2,
+			MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+			AvgCycle: 34, CollectedBy: "unknown",
+		},
+	}
+
+	matches := []TeamMatch{
+		TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 1, TeamNumber: 2343},
+		TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 2, TeamNumber: 5443},
+		TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
+			Alliance: "R", AlliancePosition: 3, TeamNumber: 5436},
+		TeamMatch{MatchNumber: 2, SetNumber: 1, CompLevel: "quals",
+			Alliance: "B", AlliancePosition: 1, TeamNumber: 5643},
+	}
+
+	for _, match := range matches {
+		err := fixture.db.AddToMatch(match)
+		check(t, err, "Failed to add match")
+	}
+
+	for i := 0; i < len(correct); i++ {
+		err := fixture.db.AddToStats2023(correct[i])
+		check(t, err, fmt.Sprint("Failed to add stats ", i))
+	}
+
+	got, err := fixture.db.ReturnStats2023()
+	check(t, err, "Failed ReturnStats()")
+
+	if !reflect.DeepEqual(correct, got) {
+		t.Errorf("Got %#v,\nbut expected %#v.", got, correct)
+	}
+}
+
 func TestReturnActionsDB(t *testing.T) {
 	fixture := createDatabase(t)
 	defer fixture.TearDown()
diff --git a/scouting/webserver/requests/BUILD b/scouting/webserver/requests/BUILD
index e0d3e87..4c4870c 100644
--- a/scouting/webserver/requests/BUILD
+++ b/scouting/webserver/requests/BUILD
@@ -9,6 +9,8 @@
     deps = [
         "//scouting/db",
         "//scouting/webserver/requests/messages:error_response_go_fbs",
+        "//scouting/webserver/requests/messages:request_2023_data_scouting_go_fbs",
+        "//scouting/webserver/requests/messages:request_2023_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_driver_rankings_go_fbs",
         "//scouting/webserver/requests/messages:request_all_driver_rankings_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_matches_go_fbs",
@@ -45,6 +47,8 @@
         "//scouting/db",
         "//scouting/webserver/requests/debug",
         "//scouting/webserver/requests/messages:error_response_go_fbs",
+        "//scouting/webserver/requests/messages:request_2023_data_scouting_go_fbs",
+        "//scouting/webserver/requests/messages:request_2023_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_driver_rankings_go_fbs",
         "//scouting/webserver/requests/messages:request_all_driver_rankings_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_matches_go_fbs",
diff --git a/scouting/webserver/requests/debug/BUILD b/scouting/webserver/requests/debug/BUILD
index 1473b3c..355fff0 100644
--- a/scouting/webserver/requests/debug/BUILD
+++ b/scouting/webserver/requests/debug/BUILD
@@ -8,6 +8,7 @@
     visibility = ["//visibility:public"],
     deps = [
         "//scouting/webserver/requests/messages:error_response_go_fbs",
+        "//scouting/webserver/requests/messages:request_2023_data_scouting_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_driver_rankings_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_matches_response_go_fbs",
         "//scouting/webserver/requests/messages:request_all_notes_response_go_fbs",
diff --git a/scouting/webserver/requests/debug/cli/main.go b/scouting/webserver/requests/debug/cli/main.go
index 9279dba..58e24a2 100644
--- a/scouting/webserver/requests/debug/cli/main.go
+++ b/scouting/webserver/requests/debug/cli/main.go
@@ -93,6 +93,8 @@
 		"If specified, parse the file as a RequestAllMatches JSON request.")
 	requestDataScoutingPtr := flag.String("requestDataScouting", "",
 		"If specified, parse the file as a RequestDataScouting JSON request.")
+	request2023DataScoutingPtr := flag.String("request2023DataScouting", "",
+		"If specified, parse the file as a Request2023DataScouting JSON request.")
 	requestAllDriverRankingsPtr := flag.String("requestAllDriverRankings", "",
 		"If specified, parse the file as a requestAllDriverRankings JSON request.")
 	requestAllNotesPtr := flag.String("requestAllNotes", "",
@@ -141,6 +143,13 @@
 		debug.RequestDataScouting)
 
 	maybePerformRequest(
+		"Request2023DataScouting",
+		"scouting/webserver/requests/messages/request_2023_data_scouting.fbs",
+		*request2023DataScoutingPtr,
+		*addressPtr,
+		debug.Request2023DataScouting)
+
+	maybePerformRequest(
 		"requestAllDriverRankings",
 		"scouting/webserver/requests/messages/request_all_driver_rankings.fbs",
 		*requestAllDriverRankingsPtr,
diff --git a/scouting/webserver/requests/debug/debug.go b/scouting/webserver/requests/debug/debug.go
index 1f215f7..60d14be 100644
--- a/scouting/webserver/requests/debug/debug.go
+++ b/scouting/webserver/requests/debug/debug.go
@@ -10,6 +10,7 @@
 	"net/http"
 
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
 	"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_all_notes_response"
@@ -129,6 +130,12 @@
 		request_data_scouting_response.GetRootAsRequestDataScoutingResponse)
 }
 
+func Request2023DataScouting(server string, requestBytes []byte) (*request_2023_data_scouting_response.Request2023DataScoutingResponseT, error) {
+	return sendMessage[request_2023_data_scouting_response.Request2023DataScoutingResponseT](
+		server+"/requests/request/2023_data_scouting", requestBytes,
+		request_2023_data_scouting_response.GetRootAsRequest2023DataScoutingResponse)
+}
+
 func SubmitNotes(server string, requestBytes []byte) (*submit_notes_response.SubmitNotesResponseT, error) {
 	return sendMessage[submit_notes_response.SubmitNotesResponseT](
 		server+"/requests/submit/submit_notes", requestBytes,
diff --git a/scouting/webserver/requests/messages/BUILD b/scouting/webserver/requests/messages/BUILD
index 124613c..a04f39f 100644
--- a/scouting/webserver/requests/messages/BUILD
+++ b/scouting/webserver/requests/messages/BUILD
@@ -13,6 +13,8 @@
     "request_all_notes_response",
     "request_data_scouting",
     "request_data_scouting_response",
+    "request_2023_data_scouting",
+    "request_2023_data_scouting_response",
     "submit_notes",
     "submit_notes_response",
     "request_notes_for_team",
diff --git a/scouting/webserver/requests/messages/request_2023_data_scouting.fbs b/scouting/webserver/requests/messages/request_2023_data_scouting.fbs
new file mode 100644
index 0000000..e54c08f
--- /dev/null
+++ b/scouting/webserver/requests/messages/request_2023_data_scouting.fbs
@@ -0,0 +1,7 @@
+namespace scouting.webserver.requests;
+
+table Request2023DataScouting {
+
+}
+
+root_type Request2023DataScouting;
diff --git a/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
new file mode 100644
index 0000000..d9d36b3
--- /dev/null
+++ b/scouting/webserver/requests/messages/request_2023_data_scouting_response.fbs
@@ -0,0 +1,36 @@
+namespace scouting.webserver.requests;
+
+table Stats2023 {
+  team_number:string (id: 0);
+  match_number:int (id: 1);
+  set_number:int (id: 21);
+  comp_level:string (id: 22);
+
+  starting_quadrant:int (id: 2);
+  low_cubes_auto:int (id:3);
+  middle_cubes_auto:int (id:4);
+  high_cubes_auto:int (id: 5);
+  cubes_dropped_auto: int (id: 6);
+  low_cones_auto:int (id:7);
+  middle_cones_auto:int (id:8);
+  high_cones_auto:int (id:9);
+  cones_dropped_auto:int (id:10);
+
+  low_cubes:int (id:11);
+  middle_cubes:int (id:12);
+  high_cubes:int (id:13);
+  cubes_dropped:int (id:14);
+  low_cones:int (id:15);
+  middle_cones:int (id:16);
+  high_cones:int (id:17);
+  cones_dropped:int (id:18);
+  avg_cycle:int (id:19);
+
+  collected_by:string (id:20);
+}
+
+table Request2023DataScoutingResponse {
+    stats_list:[Stats2023] (id:0);
+}
+
+root_type Request2023DataScoutingResponse;
\ No newline at end of file
diff --git a/scouting/webserver/requests/requests.go b/scouting/webserver/requests/requests.go
index fab725c..c59e7c5 100644
--- a/scouting/webserver/requests/requests.go
+++ b/scouting/webserver/requests/requests.go
@@ -13,6 +13,8 @@
 
 	"github.com/frc971/971-Robot-Code/scouting/db"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
@@ -49,6 +51,8 @@
 type RequestAllNotesResponseT = request_all_notes_response.RequestAllNotesResponseT
 type RequestDataScouting = request_data_scouting.RequestDataScouting
 type RequestDataScoutingResponseT = request_data_scouting_response.RequestDataScoutingResponseT
+type Request2023DataScouting = request_2023_data_scouting.Request2023DataScouting
+type Request2023DataScoutingResponseT = request_2023_data_scouting_response.Request2023DataScoutingResponseT
 type SubmitNotes = submit_notes.SubmitNotes
 type SubmitNotesResponseT = submit_notes_response.SubmitNotesResponseT
 type RequestNotesForTeam = request_notes_for_team.RequestNotesForTeam
@@ -68,11 +72,13 @@
 	AddToMatch(db.TeamMatch) error
 	AddToShift(db.Shift) error
 	AddToStats(db.Stats) error
+	AddToStats2023(db.Stats2023) error
 	ReturnMatches() ([]db.TeamMatch, error)
 	ReturnAllNotes() ([]db.NotesData, error)
 	ReturnAllDriverRankings() ([]db.DriverRankingData, error)
 	ReturnAllShifts() ([]db.Shift, error)
 	ReturnStats() ([]db.Stats, error)
+	ReturnStats2023() ([]db.Stats2023, error)
 	QueryAllShifts(int) ([]db.Shift, error)
 	QueryStats(int) ([]db.Stats, error)
 	QueryNotes(int32) ([]string, error)
@@ -374,7 +380,7 @@
 
 	stats, err := handler.db.ReturnStats()
 	if err != nil {
-		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Faled to query database: ", err))
+		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
 		return
 	}
 
@@ -447,6 +453,63 @@
 	w.Write(builder.FinishedBytes())
 }
 
+// Handles a Request2023DataScouting request.
+type request2023DataScoutingHandler struct {
+	db Database
+}
+
+func (handler request2023DataScoutingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+	requestBytes, err := io.ReadAll(req.Body)
+	if err != nil {
+		respondWithError(w, http.StatusBadRequest, fmt.Sprint("Failed to read request bytes:", err))
+		return
+	}
+
+	_, success := parseRequest(w, requestBytes, "Request2023DataScouting", request_2023_data_scouting.GetRootAsRequest2023DataScouting)
+	if !success {
+		return
+	}
+
+	stats, err := handler.db.ReturnStats2023()
+	if err != nil {
+		respondWithError(w, http.StatusInternalServerError, fmt.Sprint("Failed to query database: ", err))
+		return
+	}
+
+	var response Request2023DataScoutingResponseT
+	for _, stat := range stats {
+		response.StatsList = append(response.StatsList, &request_2023_data_scouting_response.Stats2023T{
+			TeamNumber:       stat.TeamNumber,
+			MatchNumber:      stat.MatchNumber,
+			SetNumber:        stat.SetNumber,
+			CompLevel:        stat.CompLevel,
+			StartingQuadrant: stat.StartingQuadrant,
+			LowCubesAuto:     stat.LowCubesAuto,
+			MiddleCubesAuto:  stat.MiddleCubesAuto,
+			HighCubesAuto:    stat.HighCubesAuto,
+			CubesDroppedAuto: stat.CubesDroppedAuto,
+			LowConesAuto:     stat.LowConesAuto,
+			MiddleConesAuto:  stat.MiddleConesAuto,
+			HighConesAuto:    stat.HighConesAuto,
+			ConesDroppedAuto: stat.ConesDroppedAuto,
+			LowCubes:         stat.LowCubes,
+			MiddleCubes:      stat.MiddleCubes,
+			HighCubes:        stat.HighCubes,
+			CubesDropped:     stat.CubesDropped,
+			LowCones:         stat.LowCones,
+			MiddleCones:      stat.MiddleCones,
+			HighCones:        stat.HighCones,
+			ConesDropped:     stat.ConesDropped,
+			AvgCycle:         stat.AvgCycle,
+			CollectedBy:      stat.CollectedBy,
+		})
+	}
+
+	builder := flatbuffers.NewBuilder(50 * 1024)
+	builder.Finish((&response).Pack(builder))
+	w.Write(builder.FinishedBytes())
+}
+
 type requestNotesForTeamHandler struct {
 	db Database
 }
@@ -683,6 +746,7 @@
 	scoutingServer.Handle("/requests/request/all_notes", requestAllNotesHandler{db})
 	scoutingServer.Handle("/requests/request/all_driver_rankings", requestAllDriverRankingsHandler{db})
 	scoutingServer.Handle("/requests/request/data_scouting", requestDataScoutingHandler{db})
+	scoutingServer.Handle("/requests/request/2023_data_scouting", request2023DataScoutingHandler{db})
 	scoutingServer.Handle("/requests/submit/submit_notes", submitNoteScoutingHandler{db})
 	scoutingServer.Handle("/requests/request/notes_for_team", requestNotesForTeamHandler{db})
 	scoutingServer.Handle("/requests/submit/shift_schedule", submitShiftScheduleHandler{db})
diff --git a/scouting/webserver/requests/requests_test.go b/scouting/webserver/requests/requests_test.go
index ee22022..6df15de 100644
--- a/scouting/webserver/requests/requests_test.go
+++ b/scouting/webserver/requests/requests_test.go
@@ -10,6 +10,8 @@
 	"github.com/frc971/971-Robot-Code/scouting/db"
 	"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/request_2023_data_scouting"
+	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_2023_data_scouting_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_driver_rankings_response"
 	"github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
@@ -316,6 +318,79 @@
 	}
 }
 
+// Validates that we can request the 2023 stats.
+func TestRequest2023DataScouting(t *testing.T) {
+	db := MockDatabase{
+		stats2023: []db.Stats2023{
+			{
+				TeamNumber: "3634", MatchNumber: 1, SetNumber: 2,
+				CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 10,
+				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
+				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
+				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
+				HighCubes: 2, CubesDropped: 1, LowCones: 1,
+				MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+				AvgCycle: 34, CollectedBy: "isaac",
+			},
+			{
+				TeamNumber: "2343", MatchNumber: 1, SetNumber: 2,
+				CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 0,
+				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 2,
+				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
+				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
+				HighCubes: 1, CubesDropped: 0, LowCones: 0,
+				MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+				AvgCycle: 53, CollectedBy: "unknown",
+			},
+		},
+	}
+	scoutingServer := server.NewScoutingServer()
+	HandleRequests(&db, scoutingServer)
+	scoutingServer.Start(8080)
+	defer scoutingServer.Stop()
+
+	builder := flatbuffers.NewBuilder(1024)
+	builder.Finish((&request_2023_data_scouting.Request2023DataScoutingT{}).Pack(builder))
+
+	response, err := debug.Request2023DataScouting("http://localhost:8080", builder.FinishedBytes())
+	if err != nil {
+		t.Fatal("Failed to request all matches: ", err)
+	}
+
+	expected := request_2023_data_scouting_response.Request2023DataScoutingResponseT{
+		StatsList: []*request_2023_data_scouting_response.Stats2023T{
+			{
+				TeamNumber: "3634", MatchNumber: 1, SetNumber: 2,
+				CompLevel: "quals", StartingQuadrant: 3, LowCubesAuto: 10,
+				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 0,
+				LowConesAuto: 1, MiddleConesAuto: 2, HighConesAuto: 1,
+				ConesDroppedAuto: 0, LowCubes: 1, MiddleCubes: 1,
+				HighCubes: 2, CubesDropped: 1, LowCones: 1,
+				MiddleCones: 2, HighCones: 0, ConesDropped: 1,
+				AvgCycle: 34, CollectedBy: "isaac",
+			},
+			{
+				TeamNumber: "2343", MatchNumber: 1, SetNumber: 2,
+				CompLevel: "quals", StartingQuadrant: 1, LowCubesAuto: 0,
+				MiddleCubesAuto: 1, HighCubesAuto: 1, CubesDroppedAuto: 2,
+				LowConesAuto: 0, MiddleConesAuto: 0, HighConesAuto: 0,
+				ConesDroppedAuto: 1, LowCubes: 0, MiddleCubes: 0,
+				HighCubes: 1, CubesDropped: 0, LowCones: 0,
+				MiddleCones: 2, HighCones: 1, ConesDropped: 1,
+				AvgCycle: 53, CollectedBy: "unknown",
+			},
+		},
+	}
+	if len(expected.StatsList) != len(response.StatsList) {
+		t.Fatal("Expected ", expected, ", but got ", *response)
+	}
+	for i, match := range expected.StatsList {
+		if !reflect.DeepEqual(*match, *response.StatsList[i]) {
+			t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
+		}
+	}
+}
+
 func TestSubmitNotes(t *testing.T) {
 	database := MockDatabase{}
 	scoutingServer := server.NewScoutingServer()
@@ -665,6 +740,7 @@
 	notes          []db.NotesData
 	shiftSchedule  []db.Shift
 	driver_ranking []db.DriverRankingData
+	stats2023      []db.Stats2023
 }
 
 func (database *MockDatabase) AddToMatch(match db.TeamMatch) error {
@@ -677,6 +753,10 @@
 	return nil
 }
 
+func (database *MockDatabase) AddToStats2023(stats2023 db.Stats2023) error {
+	database.stats2023 = append(database.stats2023, stats2023)
+	return nil
+}
 func (database *MockDatabase) ReturnMatches() ([]db.TeamMatch, error) {
 	return database.matches, nil
 }
@@ -685,6 +765,10 @@
 	return database.stats, nil
 }
 
+func (database *MockDatabase) ReturnStats2023() ([]db.Stats2023, error) {
+	return database.stats2023, nil
+}
+
 func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
 	return []db.Stats{}, nil
 }