blob: 12d918bfac93d33acfd5c0dca00d84a9bd11389a [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"
Alex Perry81f96ba2022-03-13 18:26:19 -070022 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/request_notes_for_team"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080023 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting"
Philipp Schrader30005e42022-03-06 13:53:58 -080024 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
Alex Perry81f96ba2022-03-13 18:26:19 -070025 "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_notes"
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080026 "github.com/frc971/971-Robot-Code/scouting/webserver/server"
27 flatbuffers "github.com/google/flatbuffers/go"
28)
29
30// Validates that an unhandled address results in a 404.
31func Test404(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080032 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080033 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080034 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080035 scoutingServer.Start(8080)
36 defer scoutingServer.Stop()
37
38 resp, err := http.Get("http://localhost:8080/requests/foo")
39 if err != nil {
40 t.Fatalf("Failed to get data: %v", err)
41 }
42 if resp.StatusCode != http.StatusNotFound {
43 t.Fatalf("Expected error code 404, but got %d instead", resp.Status)
44 }
45}
46
47// Validates that we can submit new data scouting data.
48func TestSubmitDataScoutingError(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080049 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080050 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080051 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080052 scoutingServer.Start(8080)
53 defer scoutingServer.Stop()
54
55 resp, err := http.Post("http://localhost:8080/requests/submit/data_scouting", "application/octet-stream", bytes.NewReader([]byte("")))
56 if err != nil {
57 t.Fatalf("Failed to send request: %v", err)
58 }
59 if resp.StatusCode != http.StatusBadRequest {
60 t.Fatal("Unexpected status code. Got", resp.Status)
61 }
62
63 responseBytes, err := io.ReadAll(resp.Body)
64 if err != nil {
65 t.Fatal("Failed to read response bytes:", err)
66 }
67 errorResponse := error_response.GetRootAsErrorResponse(responseBytes, 0)
68
69 errorMessage := string(errorResponse.ErrorMessage())
70 if errorMessage != "Failed to parse SubmitDataScouting: runtime error: index out of range [3] with length 0" {
71 t.Fatal("Got mismatched error message:", errorMessage)
72 }
73}
74
75// Validates that we can submit new data scouting data.
76func TestSubmitDataScouting(t *testing.T) {
Philipp Schrader8747f1b2022-02-23 23:56:22 -080077 db := MockDatabase{}
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080078 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -080079 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080080 scoutingServer.Start(8080)
81 defer scoutingServer.Stop()
82
83 builder := flatbuffers.NewBuilder(1024)
84 builder.Finish((&submit_data_scouting.SubmitDataScoutingT{
Philipp Schraderfee07e12022-03-17 22:19:47 -070085 Team: 971,
86 Match: 1,
87 StartingQuadrant: 2,
88 AutoBall1: true,
89 AutoBall2: false,
90 AutoBall3: false,
91 AutoBall4: false,
92 AutoBall5: false,
93 MissedShotsAuto: 9971,
94 UpperGoalAuto: 9971,
95 LowerGoalAuto: 9971,
96 MissedShotsTele: 9971,
97 UpperGoalTele: 9971,
98 LowerGoalTele: 9971,
99 DefenseRating: 9971,
100 Climbing: 9971,
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800101 }).Pack(builder))
102
Philipp Schrader30005e42022-03-06 13:53:58 -0800103 response, err := debug.SubmitDataScouting("http://localhost:8080", builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800104 if err != nil {
Philipp Schrader30005e42022-03-06 13:53:58 -0800105 t.Fatal("Failed to submit data scouting: ", err)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800106 }
Philipp Schrader30005e42022-03-06 13:53:58 -0800107
108 // We get an empty response back. Validate that.
109 expected := submit_data_scouting_response.SubmitDataScoutingResponseT{}
110 if !reflect.DeepEqual(expected, *response) {
111 t.Fatal("Expected ", expected, ", but got:", *response)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800112 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800113}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800114
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800115// Validates that we can request the full match list.
116func TestRequestAllMatches(t *testing.T) {
117 db := MockDatabase{
118 matches: []db.Match{
119 {
120 MatchNumber: 1, Round: 1, CompLevel: "qual",
121 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
122 },
123 {
124 MatchNumber: 2, Round: 1, CompLevel: "qual",
125 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
126 },
127 {
128 MatchNumber: 3, Round: 1, CompLevel: "qual",
129 R1: 7, R2: 44, R3: 602, B1: 973, B2: 402, B3: 202,
130 },
131 },
132 }
133 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800134 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800135 scoutingServer.Start(8080)
136 defer scoutingServer.Stop()
137
138 builder := flatbuffers.NewBuilder(1024)
139 builder.Finish((&request_all_matches.RequestAllMatchesT{}).Pack(builder))
140
141 response, err := debug.RequestAllMatches("http://localhost:8080", builder.FinishedBytes())
142 if err != nil {
143 t.Fatal("Failed to request all matches: ", err)
144 }
145
146 expected := request_all_matches_response.RequestAllMatchesResponseT{
147 MatchList: []*request_all_matches_response.MatchT{
148 // MatchNumber, Round, CompLevel
149 // R1, R2, R3, B1, B2, B3
150 {
151 1, 1, "qual",
152 5, 42, 600, 971, 400, 200,
153 },
154 {
155 2, 1, "qual",
156 6, 43, 601, 972, 401, 201,
157 },
158 {
159 3, 1, "qual",
160 7, 44, 602, 973, 402, 202,
161 },
162 },
163 }
164 if len(expected.MatchList) != len(response.MatchList) {
165 t.Fatal("Expected ", expected, ", but got ", *response)
166 }
167 for i, match := range expected.MatchList {
168 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
169 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
170 }
171 }
Philipp Schrader30005e42022-03-06 13:53:58 -0800172
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800173}
174
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800175// Validates that we can request the full match list.
176func TestRequestMatchesForTeam(t *testing.T) {
177 db := MockDatabase{
178 matches: []db.Match{
179 {
180 MatchNumber: 1, Round: 1, CompLevel: "qual",
181 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
182 },
183 {
184 MatchNumber: 2, Round: 1, CompLevel: "qual",
185 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
186 },
187 },
188 }
189 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800190 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800191 scoutingServer.Start(8080)
192 defer scoutingServer.Stop()
193
194 builder := flatbuffers.NewBuilder(1024)
195 builder.Finish((&request_matches_for_team.RequestMatchesForTeamT{
196 Team: 971,
197 }).Pack(builder))
198
199 response, err := debug.RequestMatchesForTeam("http://localhost:8080", builder.FinishedBytes())
200 if err != nil {
201 t.Fatal("Failed to request all matches: ", err)
202 }
203
204 expected := request_matches_for_team_response.RequestMatchesForTeamResponseT{
205 MatchList: []*request_matches_for_team_response.MatchT{
206 // MatchNumber, Round, CompLevel
207 // R1, R2, R3, B1, B2, B3
208 {
209 1, 1, "qual",
210 5, 42, 600, 971, 400, 200,
211 },
212 },
213 }
214 if len(expected.MatchList) != len(response.MatchList) {
215 t.Fatal("Expected ", expected, ", but got ", *response)
216 }
217 for i, match := range expected.MatchList {
218 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
219 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
220 }
221 }
222}
223
Philipp Schraderacf96232022-03-01 22:03:30 -0800224// Validates that we can request the stats.
225func TestRequestDataScouting(t *testing.T) {
226 db := MockDatabase{
227 stats: []db.Stats{
228 {
229 TeamNumber: 971, MatchNumber: 1,
Philipp Schraderfee07e12022-03-17 22:19:47 -0700230 StartingQuadrant: 1,
231 AutoBallPickedUp: [5]bool{true, false, false, false, true},
232 ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
Philipp Schraderacf96232022-03-01 22:03:30 -0800233 ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
234 PlayedDefense: 7, Climbing: 8,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700235 CollectedBy: "john",
Philipp Schraderacf96232022-03-01 22:03:30 -0800236 },
237 {
238 TeamNumber: 972, MatchNumber: 1,
Philipp Schraderfee07e12022-03-17 22:19:47 -0700239 StartingQuadrant: 2,
240 AutoBallPickedUp: [5]bool{false, false, true, false, false},
241 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
Philipp Schraderacf96232022-03-01 22:03:30 -0800242 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
243 PlayedDefense: 8, Climbing: 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700244 CollectedBy: "andrea",
Philipp Schraderacf96232022-03-01 22:03:30 -0800245 },
246 },
247 }
248 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800249 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderacf96232022-03-01 22:03:30 -0800250 scoutingServer.Start(8080)
251 defer scoutingServer.Stop()
252
253 builder := flatbuffers.NewBuilder(1024)
254 builder.Finish((&request_data_scouting.RequestDataScoutingT{}).Pack(builder))
255
256 response, err := debug.RequestDataScouting("http://localhost:8080", builder.FinishedBytes())
257 if err != nil {
258 t.Fatal("Failed to request all matches: ", err)
259 }
260
261 expected := request_data_scouting_response.RequestDataScoutingResponseT{
262 StatsList: []*request_data_scouting_response.StatsT{
263 // Team, Match,
264 // MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
265 // MissedShotsTele, UpperGoalTele, LowerGoalTele,
266 // DefenseRating, Climbing,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700267 // CollectedBy,
Philipp Schraderfee07e12022-03-17 22:19:47 -0700268 // AutoBall1, AutoBall2, AutoBall3,
269 // AutoBall4, AutoBall5,
270 // StartingQuadrant,
Philipp Schraderacf96232022-03-01 22:03:30 -0800271 {
272 971, 1,
273 4, 5, 6,
274 1, 2, 3,
275 7, 8,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700276 "john",
Philipp Schraderfee07e12022-03-17 22:19:47 -0700277 true, false, false,
278 false, true,
279 1,
Philipp Schraderacf96232022-03-01 22:03:30 -0800280 },
281 {
282 972, 1,
283 5, 6, 7,
284 2, 3, 4,
285 8, 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700286 "andrea",
Philipp Schraderfee07e12022-03-17 22:19:47 -0700287 false, false, true,
288 false, false,
289 2,
Philipp Schraderacf96232022-03-01 22:03:30 -0800290 },
291 },
292 }
293 if len(expected.StatsList) != len(response.StatsList) {
294 t.Fatal("Expected ", expected, ", but got ", *response)
295 }
296 for i, match := range expected.StatsList {
297 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
298 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
299 }
300 }
301}
302
Alex Perry81f96ba2022-03-13 18:26:19 -0700303func TestSubmitNotes(t *testing.T) {
304 database := MockDatabase{}
305 scoutingServer := server.NewScoutingServer()
306 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
307 scoutingServer.Start(8080)
308 defer scoutingServer.Stop()
309
310 builder := flatbuffers.NewBuilder(1024)
311 builder.Finish((&submit_notes.SubmitNotesT{
312 Team: 971,
313 Notes: "Notes",
314 }).Pack(builder))
315
316 _, err := debug.SubmitNotes("http://localhost:8080", builder.FinishedBytes())
317 if err != nil {
318 t.Fatal("Failed to submit notes: ", err)
319 }
320
321 expected := []db.NotesData{
322 {TeamNumber: 971, Notes: []string{"Notes"}},
323 }
324
325 if !reflect.DeepEqual(database.notes, expected) {
326 t.Fatal("Submitted notes did not match", expected, database.notes)
327 }
328}
329
330func TestRequestNotes(t *testing.T) {
331 database := MockDatabase{
332 notes: []db.NotesData{{
333 TeamNumber: 971,
334 Notes: []string{"Notes"},
335 }},
336 }
337 scoutingServer := server.NewScoutingServer()
338 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
339 scoutingServer.Start(8080)
340 defer scoutingServer.Stop()
341
342 builder := flatbuffers.NewBuilder(1024)
343 builder.Finish((&request_notes_for_team.RequestNotesForTeamT{
344 Team: 971,
345 }).Pack(builder))
346 response, err := debug.RequestNotes("http://localhost:8080", builder.FinishedBytes())
347 if err != nil {
348 t.Fatal("Failed to submit notes: ", err)
349 }
350
351 if response.Notes[0].Data != "Notes" {
352 t.Fatal("requested notes did not match", response)
353 }
354}
355
Philipp Schraderd3fac192022-03-02 20:35:46 -0800356// Validates that we can download the schedule from The Blue Alliance.
357func TestRefreshMatchList(t *testing.T) {
358 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
359 return []scraping.Match{
360 {
361 CompLevel: "qual",
362 MatchNumber: 1,
363 Alliances: scraping.Alliances{
364 Red: scraping.Alliance{
365 TeamKeys: []string{
366 "100",
367 "200",
368 "300",
369 },
370 },
371 Blue: scraping.Alliance{
372 TeamKeys: []string{
373 "101",
374 "201",
375 "301",
376 },
377 },
378 },
379 WinningAlliance: "",
380 EventKey: "",
381 Time: 0,
382 PredictedTime: 0,
383 ActualTime: 0,
384 PostResultTime: 0,
385 ScoreBreakdowns: scraping.ScoreBreakdowns{},
386 },
387 }, nil
388 }
389
390 database := MockDatabase{}
391 scoutingServer := server.NewScoutingServer()
392 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
393 scoutingServer.Start(8080)
394 defer scoutingServer.Stop()
395
396 builder := flatbuffers.NewBuilder(1024)
397 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
398
399 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
400 if err != nil {
401 t.Fatal("Failed to request all matches: ", err)
402 }
403
404 // Validate the response.
405 expected := refresh_match_list_response.RefreshMatchListResponseT{}
406 if !reflect.DeepEqual(expected, *response) {
407 t.Fatal("Expected ", expected, ", but got ", *response)
408 }
409
410 // Make sure that the data made it into the database.
411 expectedMatches := []db.Match{
412 {
413 MatchNumber: 1,
414 Round: 1,
415 CompLevel: "qual",
416 R1: 100,
417 R2: 200,
418 R3: 300,
419 B1: 101,
420 B2: 201,
421 B3: 301,
422 },
423 }
424 if !reflect.DeepEqual(expectedMatches, database.matches) {
425 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
426 }
427}
428
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800429// A mocked database we can use for testing. Add functionality to this as
430// needed for your tests.
431
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800432type MockDatabase struct {
433 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800434 stats []db.Stats
Alex Perry81f96ba2022-03-13 18:26:19 -0700435 notes []db.NotesData
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800436}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800437
Philipp Schraderd3fac192022-03-02 20:35:46 -0800438func (database *MockDatabase) AddToMatch(match db.Match) error {
439 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800440 return nil
441}
442
Philipp Schrader30005e42022-03-06 13:53:58 -0800443func (database *MockDatabase) AddToStats(stats db.Stats) error {
444 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800445 return nil
446}
447
448func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800449 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800450}
451
452func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800453 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800454}
455
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800456func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
457 var matches []db.Match
458 for _, match := range database.matches {
459 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
460 if team == requestedTeam {
461 matches = append(matches, match)
462 break
463 }
464 }
465 }
466 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800467}
468
469func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
470 return []db.Stats{}, nil
471}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800472
Alex Perry81f96ba2022-03-13 18:26:19 -0700473func (database *MockDatabase) QueryNotes(requestedTeam int32) (db.NotesData, error) {
474 var results []string
475 for _, data := range database.notes {
476 if data.TeamNumber == requestedTeam {
477 results = append(results, data.Notes[0])
478 }
479 }
480 return db.NotesData{TeamNumber: requestedTeam, Notes: results}, nil
481}
482
483func (database *MockDatabase) AddNotes(data db.NotesData) error {
484 database.notes = append(database.notes, data)
485 return nil
486}
487
Philipp Schraderd3fac192022-03-02 20:35:46 -0800488// Returns an empty match list from the fake The Blue Alliance scraping.
489func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
490 return nil, nil
491}