blob: 0183736c76c36667e6a793a47b672f5d34b4f2c3 [file] [log] [blame]
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08001package requests
2
3import (
4 "bytes"
5 "io"
6 "net/http"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -08007 "reflect"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -08008 "testing"
9
Philipp Schrader8747f1b2022-02-23 23:56:22 -080010 "github.com/frc971/971-Robot-Code/scouting/db"
Philipp Schraderd3fac192022-03-02 20:35:46 -080011 "github.com/frc971/971-Robot-Code/scouting/scraping"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080012 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/debug"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080013 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/error_response"
Philipp Schraderd3fac192022-03-02 20:35:46 -080014 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list"
15 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/refresh_match_list_response"
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080016 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches"
17 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_all_matches_response"
Philipp Schraderacf96232022-03-01 22:03:30 -080018 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting"
19 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_data_scouting_response"
Philipp Schraderd1c4bef2022-02-28 22:51:30 -080020 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team"
21 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_matches_for_team_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080023 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
25 flatbuffers "github.com/google/flatbuffers/go"
26)
27
28// Validates that an unhandled address results in a 404.
29func Test404(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080030 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080031 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080032 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080033 scoutingServer.Start(8080)
34 defer scoutingServer.Stop()
35
36 resp, err := http.Get("http://localhost:8080/requests/foo")
37 if err != nil {
38 t.Fatalf("Failed to get data: %v", err)
39 }
40 if resp.StatusCode != http.StatusNotFound {
41 t.Fatalf("Expected error code 404, but got %d instead", resp.Status)
42 }
43}
44
45// Validates that we can submit new data scouting data.
46func TestSubmitDataScoutingError(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080047 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080048 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080049 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080050 scoutingServer.Start(8080)
51 defer scoutingServer.Stop()
52
53 resp, err := http.Post("http://localhost:8080/requests/submit/data_scouting", "application/octet-stream", bytes.NewReader([]byte("")))
54 if err != nil {
55 t.Fatalf("Failed to send request: %v", err)
56 }
57 if resp.StatusCode != http.StatusBadRequest {
58 t.Fatal("Unexpected status code. Got", resp.Status)
59 }
60
61 responseBytes, err := io.ReadAll(resp.Body)
62 if err != nil {
63 t.Fatal("Failed to read response bytes:", err)
64 }
65 errorResponse := error_response.GetRootAsErrorResponse(responseBytes, 0)
66
67 errorMessage := string(errorResponse.ErrorMessage())
68 if errorMessage != "Failed to parse SubmitDataScouting: runtime error: index out of range [3] with length 0" {
69 t.Fatal("Got mismatched error message:", errorMessage)
70 }
71}
72
73// Validates that we can submit new data scouting data.
74func TestSubmitDataScouting(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080075 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080076 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080077 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080078 scoutingServer.Start(8080)
79 defer scoutingServer.Stop()
80
81 builder := flatbuffers.NewBuilder(1024)
82 builder.Finish((&submit_data_scouting.SubmitDataScoutingT{
Sabina Leavere66c2fc2022-02-24 16:56:15 -080083 Team: 971,
84 Match: 1,
85 MissedShotsAuto: 9971,
86 UpperGoalAuto: 9971,
87 LowerGoalAuto: 9971,
88 MissedShotsTele: 9971,
89 UpperGoalTele: 9971,
90 LowerGoalTele: 9971,
91 DefenseRating: 9971,
92 Climbing: 9971,
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080093 }).Pack(builder))
94
Philipp Schrader30005e42022-03-06 13:53:58 -080095 response, err := debug.SubmitDataScouting("http://localhost:8080", builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080096 if err != nil {
Philipp Schrader30005e42022-03-06 13:53:58 -080097 t.Fatal("Failed to submit data scouting: ", err)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080098 }
Philipp Schrader30005e42022-03-06 13:53:58 -080099
100 // We get an empty response back. Validate that.
101 expected := submit_data_scouting_response.SubmitDataScoutingResponseT{}
102 if !reflect.DeepEqual(expected, *response) {
103 t.Fatal("Expected ", expected, ", but got:", *response)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800104 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800105}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800106
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800107// Validates that we can request the full match list.
108func TestRequestAllMatches(t *testing.T) {
109 db := MockDatabase{
110 matches: []db.Match{
111 {
112 MatchNumber: 1, Round: 1, CompLevel: "qual",
113 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
114 },
115 {
116 MatchNumber: 2, Round: 1, CompLevel: "qual",
117 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
118 },
119 {
120 MatchNumber: 3, Round: 1, CompLevel: "qual",
121 R1: 7, R2: 44, R3: 602, B1: 973, B2: 402, B3: 202,
122 },
123 },
124 }
125 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800126 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800127 scoutingServer.Start(8080)
128 defer scoutingServer.Stop()
129
130 builder := flatbuffers.NewBuilder(1024)
131 builder.Finish((&request_all_matches.RequestAllMatchesT{}).Pack(builder))
132
133 response, err := debug.RequestAllMatches("http://localhost:8080", builder.FinishedBytes())
134 if err != nil {
135 t.Fatal("Failed to request all matches: ", err)
136 }
137
138 expected := request_all_matches_response.RequestAllMatchesResponseT{
139 MatchList: []*request_all_matches_response.MatchT{
140 // MatchNumber, Round, CompLevel
141 // R1, R2, R3, B1, B2, B3
142 {
143 1, 1, "qual",
144 5, 42, 600, 971, 400, 200,
145 },
146 {
147 2, 1, "qual",
148 6, 43, 601, 972, 401, 201,
149 },
150 {
151 3, 1, "qual",
152 7, 44, 602, 973, 402, 202,
153 },
154 },
155 }
156 if len(expected.MatchList) != len(response.MatchList) {
157 t.Fatal("Expected ", expected, ", but got ", *response)
158 }
159 for i, match := range expected.MatchList {
160 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
161 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
162 }
163 }
Philipp Schrader30005e42022-03-06 13:53:58 -0800164
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800165}
166
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800167// Validates that we can request the full match list.
168func TestRequestMatchesForTeam(t *testing.T) {
169 db := MockDatabase{
170 matches: []db.Match{
171 {
172 MatchNumber: 1, Round: 1, CompLevel: "qual",
173 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
174 },
175 {
176 MatchNumber: 2, Round: 1, CompLevel: "qual",
177 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
178 },
179 },
180 }
181 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800182 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800183 scoutingServer.Start(8080)
184 defer scoutingServer.Stop()
185
186 builder := flatbuffers.NewBuilder(1024)
187 builder.Finish((&request_matches_for_team.RequestMatchesForTeamT{
188 Team: 971,
189 }).Pack(builder))
190
191 response, err := debug.RequestMatchesForTeam("http://localhost:8080", builder.FinishedBytes())
192 if err != nil {
193 t.Fatal("Failed to request all matches: ", err)
194 }
195
196 expected := request_matches_for_team_response.RequestMatchesForTeamResponseT{
197 MatchList: []*request_matches_for_team_response.MatchT{
198 // MatchNumber, Round, CompLevel
199 // R1, R2, R3, B1, B2, B3
200 {
201 1, 1, "qual",
202 5, 42, 600, 971, 400, 200,
203 },
204 },
205 }
206 if len(expected.MatchList) != len(response.MatchList) {
207 t.Fatal("Expected ", expected, ", but got ", *response)
208 }
209 for i, match := range expected.MatchList {
210 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
211 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
212 }
213 }
214}
215
Philipp Schraderacf96232022-03-01 22:03:30 -0800216// Validates that we can request the stats.
217func TestRequestDataScouting(t *testing.T) {
218 db := MockDatabase{
219 stats: []db.Stats{
220 {
221 TeamNumber: 971, MatchNumber: 1,
222 ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
223 ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
224 PlayedDefense: 7, Climbing: 8,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700225 CollectedBy: "john",
Philipp Schraderacf96232022-03-01 22:03:30 -0800226 },
227 {
228 TeamNumber: 972, MatchNumber: 1,
229 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
230 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
231 PlayedDefense: 8, Climbing: 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700232 CollectedBy: "andrea",
Philipp Schraderacf96232022-03-01 22:03:30 -0800233 },
234 },
235 }
236 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800237 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderacf96232022-03-01 22:03:30 -0800238 scoutingServer.Start(8080)
239 defer scoutingServer.Stop()
240
241 builder := flatbuffers.NewBuilder(1024)
242 builder.Finish((&request_data_scouting.RequestDataScoutingT{}).Pack(builder))
243
244 response, err := debug.RequestDataScouting("http://localhost:8080", builder.FinishedBytes())
245 if err != nil {
246 t.Fatal("Failed to request all matches: ", err)
247 }
248
249 expected := request_data_scouting_response.RequestDataScoutingResponseT{
250 StatsList: []*request_data_scouting_response.StatsT{
251 // Team, Match,
252 // MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
253 // MissedShotsTele, UpperGoalTele, LowerGoalTele,
254 // DefenseRating, Climbing,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700255 // CollectedBy,
Philipp Schraderacf96232022-03-01 22:03:30 -0800256 {
257 971, 1,
258 4, 5, 6,
259 1, 2, 3,
260 7, 8,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700261 "john",
Philipp Schraderacf96232022-03-01 22:03:30 -0800262 },
263 {
264 972, 1,
265 5, 6, 7,
266 2, 3, 4,
267 8, 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700268 "andrea",
Philipp Schraderacf96232022-03-01 22:03:30 -0800269 },
270 },
271 }
272 if len(expected.StatsList) != len(response.StatsList) {
273 t.Fatal("Expected ", expected, ", but got ", *response)
274 }
275 for i, match := range expected.StatsList {
276 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
277 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
278 }
279 }
280}
281
Philipp Schraderd3fac192022-03-02 20:35:46 -0800282// Validates that we can download the schedule from The Blue Alliance.
283func TestRefreshMatchList(t *testing.T) {
284 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
285 return []scraping.Match{
286 {
287 CompLevel: "qual",
288 MatchNumber: 1,
289 Alliances: scraping.Alliances{
290 Red: scraping.Alliance{
291 TeamKeys: []string{
292 "100",
293 "200",
294 "300",
295 },
296 },
297 Blue: scraping.Alliance{
298 TeamKeys: []string{
299 "101",
300 "201",
301 "301",
302 },
303 },
304 },
305 WinningAlliance: "",
306 EventKey: "",
307 Time: 0,
308 PredictedTime: 0,
309 ActualTime: 0,
310 PostResultTime: 0,
311 ScoreBreakdowns: scraping.ScoreBreakdowns{},
312 },
313 }, nil
314 }
315
316 database := MockDatabase{}
317 scoutingServer := server.NewScoutingServer()
318 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
319 scoutingServer.Start(8080)
320 defer scoutingServer.Stop()
321
322 builder := flatbuffers.NewBuilder(1024)
323 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
324
325 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
326 if err != nil {
327 t.Fatal("Failed to request all matches: ", err)
328 }
329
330 // Validate the response.
331 expected := refresh_match_list_response.RefreshMatchListResponseT{}
332 if !reflect.DeepEqual(expected, *response) {
333 t.Fatal("Expected ", expected, ", but got ", *response)
334 }
335
336 // Make sure that the data made it into the database.
337 expectedMatches := []db.Match{
338 {
339 MatchNumber: 1,
340 Round: 1,
341 CompLevel: "qual",
342 R1: 100,
343 R2: 200,
344 R3: 300,
345 B1: 101,
346 B2: 201,
347 B3: 301,
348 },
349 }
350 if !reflect.DeepEqual(expectedMatches, database.matches) {
351 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
352 }
353}
354
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800355// A mocked database we can use for testing. Add functionality to this as
356// needed for your tests.
357
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800358type MockDatabase struct {
359 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800360 stats []db.Stats
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800361}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800362
Philipp Schraderd3fac192022-03-02 20:35:46 -0800363func (database *MockDatabase) AddToMatch(match db.Match) error {
364 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800365 return nil
366}
367
Philipp Schrader30005e42022-03-06 13:53:58 -0800368func (database *MockDatabase) AddToStats(stats db.Stats) error {
369 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800370 return nil
371}
372
373func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800374 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800375}
376
377func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800378 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800379}
380
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800381func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
382 var matches []db.Match
383 for _, match := range database.matches {
384 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
385 if team == requestedTeam {
386 matches = append(matches, match)
387 break
388 }
389 }
390 }
391 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800392}
393
394func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
395 return []db.Stats{}, nil
396}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800397
398// Returns an empty match list from the fake The Blue Alliance scraping.
399func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
400 return nil, nil
401}