blob: 8165c7defeb92a4efb93cfb38b6b362becc5995a [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,
Philipp Schrader36df73a2022-03-17 23:27:24 -0700100 ClimbLevel: submit_data_scouting.ClimbLevelLow,
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,
Philipp Schrader36df73a2022-03-17 23:27:24 -0700234 PlayedDefense: 7, Climbing: 2,
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,
Philipp Schrader36df73a2022-03-17 23:27:24 -0700243 PlayedDefense: 8, Climbing: 4,
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{
Philipp Schraderacf96232022-03-01 22:03:30 -0800263 {
Philipp Schrader36df73a2022-03-17 23:27:24 -0700264 Team: 971, Match: 1,
265 MissedShotsAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
266 MissedShotsTele: 1, UpperGoalTele: 2, LowerGoalTele: 3,
267 DefenseRating: 7,
268 CollectedBy: "john",
269 AutoBall1: true, AutoBall2: false, AutoBall3: false,
270 AutoBall4: false, AutoBall5: true,
271 StartingQuadrant: 1,
272 ClimbLevel: request_data_scouting_response.ClimbLevelFailedWithPlentyOfTime,
Philipp Schraderacf96232022-03-01 22:03:30 -0800273 },
274 {
Philipp Schrader36df73a2022-03-17 23:27:24 -0700275 Team: 972, Match: 1,
276 MissedShotsAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
277 MissedShotsTele: 2, UpperGoalTele: 3, LowerGoalTele: 4,
278 DefenseRating: 8,
279 CollectedBy: "andrea",
280 AutoBall1: false, AutoBall2: false, AutoBall3: true,
281 AutoBall4: false, AutoBall5: false,
282 StartingQuadrant: 2,
283 ClimbLevel: request_data_scouting_response.ClimbLevelMedium,
Philipp Schraderacf96232022-03-01 22:03:30 -0800284 },
285 },
286 }
287 if len(expected.StatsList) != len(response.StatsList) {
288 t.Fatal("Expected ", expected, ", but got ", *response)
289 }
290 for i, match := range expected.StatsList {
291 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
292 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
293 }
294 }
295}
296
Alex Perry81f96ba2022-03-13 18:26:19 -0700297func TestSubmitNotes(t *testing.T) {
298 database := MockDatabase{}
299 scoutingServer := server.NewScoutingServer()
300 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
301 scoutingServer.Start(8080)
302 defer scoutingServer.Stop()
303
304 builder := flatbuffers.NewBuilder(1024)
305 builder.Finish((&submit_notes.SubmitNotesT{
306 Team: 971,
307 Notes: "Notes",
308 }).Pack(builder))
309
310 _, err := debug.SubmitNotes("http://localhost:8080", builder.FinishedBytes())
311 if err != nil {
312 t.Fatal("Failed to submit notes: ", err)
313 }
314
315 expected := []db.NotesData{
316 {TeamNumber: 971, Notes: []string{"Notes"}},
317 }
318
319 if !reflect.DeepEqual(database.notes, expected) {
320 t.Fatal("Submitted notes did not match", expected, database.notes)
321 }
322}
323
324func TestRequestNotes(t *testing.T) {
325 database := MockDatabase{
326 notes: []db.NotesData{{
327 TeamNumber: 971,
328 Notes: []string{"Notes"},
329 }},
330 }
331 scoutingServer := server.NewScoutingServer()
332 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
333 scoutingServer.Start(8080)
334 defer scoutingServer.Stop()
335
336 builder := flatbuffers.NewBuilder(1024)
337 builder.Finish((&request_notes_for_team.RequestNotesForTeamT{
338 Team: 971,
339 }).Pack(builder))
340 response, err := debug.RequestNotes("http://localhost:8080", builder.FinishedBytes())
341 if err != nil {
342 t.Fatal("Failed to submit notes: ", err)
343 }
344
345 if response.Notes[0].Data != "Notes" {
346 t.Fatal("requested notes did not match", response)
347 }
348}
349
Philipp Schraderd3fac192022-03-02 20:35:46 -0800350// Validates that we can download the schedule from The Blue Alliance.
351func TestRefreshMatchList(t *testing.T) {
352 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
353 return []scraping.Match{
354 {
355 CompLevel: "qual",
356 MatchNumber: 1,
357 Alliances: scraping.Alliances{
358 Red: scraping.Alliance{
359 TeamKeys: []string{
360 "100",
361 "200",
362 "300",
363 },
364 },
365 Blue: scraping.Alliance{
366 TeamKeys: []string{
367 "101",
368 "201",
369 "301",
370 },
371 },
372 },
373 WinningAlliance: "",
374 EventKey: "",
375 Time: 0,
376 PredictedTime: 0,
377 ActualTime: 0,
378 PostResultTime: 0,
379 ScoreBreakdowns: scraping.ScoreBreakdowns{},
380 },
381 }, nil
382 }
383
384 database := MockDatabase{}
385 scoutingServer := server.NewScoutingServer()
386 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
387 scoutingServer.Start(8080)
388 defer scoutingServer.Stop()
389
390 builder := flatbuffers.NewBuilder(1024)
391 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
392
393 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
394 if err != nil {
395 t.Fatal("Failed to request all matches: ", err)
396 }
397
398 // Validate the response.
399 expected := refresh_match_list_response.RefreshMatchListResponseT{}
400 if !reflect.DeepEqual(expected, *response) {
401 t.Fatal("Expected ", expected, ", but got ", *response)
402 }
403
404 // Make sure that the data made it into the database.
405 expectedMatches := []db.Match{
406 {
407 MatchNumber: 1,
408 Round: 1,
409 CompLevel: "qual",
410 R1: 100,
411 R2: 200,
412 R3: 300,
413 B1: 101,
414 B2: 201,
415 B3: 301,
416 },
417 }
418 if !reflect.DeepEqual(expectedMatches, database.matches) {
419 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
420 }
421}
422
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800423// A mocked database we can use for testing. Add functionality to this as
424// needed for your tests.
425
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800426type MockDatabase struct {
427 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800428 stats []db.Stats
Alex Perry81f96ba2022-03-13 18:26:19 -0700429 notes []db.NotesData
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800430}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800431
Philipp Schraderd3fac192022-03-02 20:35:46 -0800432func (database *MockDatabase) AddToMatch(match db.Match) error {
433 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800434 return nil
435}
436
Philipp Schrader30005e42022-03-06 13:53:58 -0800437func (database *MockDatabase) AddToStats(stats db.Stats) error {
438 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800439 return nil
440}
441
442func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800443 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800444}
445
446func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800447 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800448}
449
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800450func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
451 var matches []db.Match
452 for _, match := range database.matches {
453 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
454 if team == requestedTeam {
455 matches = append(matches, match)
456 break
457 }
458 }
459 }
460 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800461}
462
463func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
464 return []db.Stats{}, nil
465}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800466
Alex Perry81f96ba2022-03-13 18:26:19 -0700467func (database *MockDatabase) QueryNotes(requestedTeam int32) (db.NotesData, error) {
468 var results []string
469 for _, data := range database.notes {
470 if data.TeamNumber == requestedTeam {
471 results = append(results, data.Notes[0])
472 }
473 }
474 return db.NotesData{TeamNumber: requestedTeam, Notes: results}, nil
475}
476
477func (database *MockDatabase) AddNotes(data db.NotesData) error {
478 database.notes = append(database.notes, data)
479 return nil
480}
481
Philipp Schraderd3fac192022-03-02 20:35:46 -0800482// Returns an empty match list from the fake The Blue Alliance scraping.
483func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
484 return nil, nil
485}