blob: e4169c06e1a2b0dee0ec68a588972bfc2d443418 [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{
Sabina Leavere66c2fc2022-02-24 16:56:15 -080085 Team: 971,
86 Match: 1,
87 MissedShotsAuto: 9971,
88 UpperGoalAuto: 9971,
89 LowerGoalAuto: 9971,
90 MissedShotsTele: 9971,
91 UpperGoalTele: 9971,
92 LowerGoalTele: 9971,
93 DefenseRating: 9971,
94 Climbing: 9971,
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080095 }).Pack(builder))
96
Philipp Schrader30005e42022-03-06 13:53:58 -080097 response, err := debug.SubmitDataScouting("http://localhost:8080", builder.FinishedBytes())
Philipp Schradercdb5cfc2022-02-20 14:57:07 -080098 if err != nil {
Philipp Schrader30005e42022-03-06 13:53:58 -080099 t.Fatal("Failed to submit data scouting: ", err)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800100 }
Philipp Schrader30005e42022-03-06 13:53:58 -0800101
102 // We get an empty response back. Validate that.
103 expected := submit_data_scouting_response.SubmitDataScoutingResponseT{}
104 if !reflect.DeepEqual(expected, *response) {
105 t.Fatal("Expected ", expected, ", but got:", *response)
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800106 }
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800107}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800108
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800109// Validates that we can request the full match list.
110func TestRequestAllMatches(t *testing.T) {
111 db := MockDatabase{
112 matches: []db.Match{
113 {
114 MatchNumber: 1, Round: 1, CompLevel: "qual",
115 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
116 },
117 {
118 MatchNumber: 2, Round: 1, CompLevel: "qual",
119 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
120 },
121 {
122 MatchNumber: 3, Round: 1, CompLevel: "qual",
123 R1: 7, R2: 44, R3: 602, B1: 973, B2: 402, B3: 202,
124 },
125 },
126 }
127 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800128 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800129 scoutingServer.Start(8080)
130 defer scoutingServer.Stop()
131
132 builder := flatbuffers.NewBuilder(1024)
133 builder.Finish((&request_all_matches.RequestAllMatchesT{}).Pack(builder))
134
135 response, err := debug.RequestAllMatches("http://localhost:8080", builder.FinishedBytes())
136 if err != nil {
137 t.Fatal("Failed to request all matches: ", err)
138 }
139
140 expected := request_all_matches_response.RequestAllMatchesResponseT{
141 MatchList: []*request_all_matches_response.MatchT{
142 // MatchNumber, Round, CompLevel
143 // R1, R2, R3, B1, B2, B3
144 {
145 1, 1, "qual",
146 5, 42, 600, 971, 400, 200,
147 },
148 {
149 2, 1, "qual",
150 6, 43, 601, 972, 401, 201,
151 },
152 {
153 3, 1, "qual",
154 7, 44, 602, 973, 402, 202,
155 },
156 },
157 }
158 if len(expected.MatchList) != len(response.MatchList) {
159 t.Fatal("Expected ", expected, ", but got ", *response)
160 }
161 for i, match := range expected.MatchList {
162 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
163 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
164 }
165 }
Philipp Schrader30005e42022-03-06 13:53:58 -0800166
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800167}
168
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800169// Validates that we can request the full match list.
170func TestRequestMatchesForTeam(t *testing.T) {
171 db := MockDatabase{
172 matches: []db.Match{
173 {
174 MatchNumber: 1, Round: 1, CompLevel: "qual",
175 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
176 },
177 {
178 MatchNumber: 2, Round: 1, CompLevel: "qual",
179 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
180 },
181 },
182 }
183 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800184 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800185 scoutingServer.Start(8080)
186 defer scoutingServer.Stop()
187
188 builder := flatbuffers.NewBuilder(1024)
189 builder.Finish((&request_matches_for_team.RequestMatchesForTeamT{
190 Team: 971,
191 }).Pack(builder))
192
193 response, err := debug.RequestMatchesForTeam("http://localhost:8080", builder.FinishedBytes())
194 if err != nil {
195 t.Fatal("Failed to request all matches: ", err)
196 }
197
198 expected := request_matches_for_team_response.RequestMatchesForTeamResponseT{
199 MatchList: []*request_matches_for_team_response.MatchT{
200 // MatchNumber, Round, CompLevel
201 // R1, R2, R3, B1, B2, B3
202 {
203 1, 1, "qual",
204 5, 42, 600, 971, 400, 200,
205 },
206 },
207 }
208 if len(expected.MatchList) != len(response.MatchList) {
209 t.Fatal("Expected ", expected, ", but got ", *response)
210 }
211 for i, match := range expected.MatchList {
212 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
213 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
214 }
215 }
216}
217
Philipp Schraderacf96232022-03-01 22:03:30 -0800218// Validates that we can request the stats.
219func TestRequestDataScouting(t *testing.T) {
220 db := MockDatabase{
221 stats: []db.Stats{
222 {
223 TeamNumber: 971, MatchNumber: 1,
224 ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
225 ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
226 PlayedDefense: 7, Climbing: 8,
227 },
228 {
229 TeamNumber: 972, MatchNumber: 1,
230 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
231 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
232 PlayedDefense: 8, Climbing: 9,
233 },
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,
255 {
256 971, 1,
257 4, 5, 6,
258 1, 2, 3,
259 7, 8,
260 },
261 {
262 972, 1,
263 5, 6, 7,
264 2, 3, 4,
265 8, 9,
266 },
267 },
268 }
269 if len(expected.StatsList) != len(response.StatsList) {
270 t.Fatal("Expected ", expected, ", but got ", *response)
271 }
272 for i, match := range expected.StatsList {
273 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
274 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
275 }
276 }
277}
278
Alex Perry81f96ba2022-03-13 18:26:19 -0700279func TestSubmitNotes(t *testing.T) {
280 database := MockDatabase{}
281 scoutingServer := server.NewScoutingServer()
282 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
283 scoutingServer.Start(8080)
284 defer scoutingServer.Stop()
285
286 builder := flatbuffers.NewBuilder(1024)
287 builder.Finish((&submit_notes.SubmitNotesT{
288 Team: 971,
289 Notes: "Notes",
290 }).Pack(builder))
291
292 _, err := debug.SubmitNotes("http://localhost:8080", builder.FinishedBytes())
293 if err != nil {
294 t.Fatal("Failed to submit notes: ", err)
295 }
296
297 expected := []db.NotesData{
298 {TeamNumber: 971, Notes: []string{"Notes"}},
299 }
300
301 if !reflect.DeepEqual(database.notes, expected) {
302 t.Fatal("Submitted notes did not match", expected, database.notes)
303 }
304}
305
306func TestRequestNotes(t *testing.T) {
307 database := MockDatabase{
308 notes: []db.NotesData{{
309 TeamNumber: 971,
310 Notes: []string{"Notes"},
311 }},
312 }
313 scoutingServer := server.NewScoutingServer()
314 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
315 scoutingServer.Start(8080)
316 defer scoutingServer.Stop()
317
318 builder := flatbuffers.NewBuilder(1024)
319 builder.Finish((&request_notes_for_team.RequestNotesForTeamT{
320 Team: 971,
321 }).Pack(builder))
322 response, err := debug.RequestNotes("http://localhost:8080", builder.FinishedBytes())
323 if err != nil {
324 t.Fatal("Failed to submit notes: ", err)
325 }
326
327 if response.Notes[0].Data != "Notes" {
328 t.Fatal("requested notes did not match", response)
329 }
330}
331
Philipp Schraderd3fac192022-03-02 20:35:46 -0800332// Validates that we can download the schedule from The Blue Alliance.
333func TestRefreshMatchList(t *testing.T) {
334 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
335 return []scraping.Match{
336 {
337 CompLevel: "qual",
338 MatchNumber: 1,
339 Alliances: scraping.Alliances{
340 Red: scraping.Alliance{
341 TeamKeys: []string{
342 "100",
343 "200",
344 "300",
345 },
346 },
347 Blue: scraping.Alliance{
348 TeamKeys: []string{
349 "101",
350 "201",
351 "301",
352 },
353 },
354 },
355 WinningAlliance: "",
356 EventKey: "",
357 Time: 0,
358 PredictedTime: 0,
359 ActualTime: 0,
360 PostResultTime: 0,
361 ScoreBreakdowns: scraping.ScoreBreakdowns{},
362 },
363 }, nil
364 }
365
366 database := MockDatabase{}
367 scoutingServer := server.NewScoutingServer()
368 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
369 scoutingServer.Start(8080)
370 defer scoutingServer.Stop()
371
372 builder := flatbuffers.NewBuilder(1024)
373 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
374
375 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
376 if err != nil {
377 t.Fatal("Failed to request all matches: ", err)
378 }
379
380 // Validate the response.
381 expected := refresh_match_list_response.RefreshMatchListResponseT{}
382 if !reflect.DeepEqual(expected, *response) {
383 t.Fatal("Expected ", expected, ", but got ", *response)
384 }
385
386 // Make sure that the data made it into the database.
387 expectedMatches := []db.Match{
388 {
389 MatchNumber: 1,
390 Round: 1,
391 CompLevel: "qual",
392 R1: 100,
393 R2: 200,
394 R3: 300,
395 B1: 101,
396 B2: 201,
397 B3: 301,
398 },
399 }
400 if !reflect.DeepEqual(expectedMatches, database.matches) {
401 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
402 }
403}
404
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800405// A mocked database we can use for testing. Add functionality to this as
406// needed for your tests.
407
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800408type MockDatabase struct {
409 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800410 stats []db.Stats
Alex Perry81f96ba2022-03-13 18:26:19 -0700411 notes []db.NotesData
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800412}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800413
Philipp Schraderd3fac192022-03-02 20:35:46 -0800414func (database *MockDatabase) AddToMatch(match db.Match) error {
415 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800416 return nil
417}
418
Philipp Schrader30005e42022-03-06 13:53:58 -0800419func (database *MockDatabase) AddToStats(stats db.Stats) error {
420 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800421 return nil
422}
423
424func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800425 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800426}
427
428func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800429 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800430}
431
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800432func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
433 var matches []db.Match
434 for _, match := range database.matches {
435 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
436 if team == requestedTeam {
437 matches = append(matches, match)
438 break
439 }
440 }
441 }
442 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800443}
444
445func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
446 return []db.Stats{}, nil
447}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800448
Alex Perry81f96ba2022-03-13 18:26:19 -0700449func (database *MockDatabase) QueryNotes(requestedTeam int32) (db.NotesData, error) {
450 var results []string
451 for _, data := range database.notes {
452 if data.TeamNumber == requestedTeam {
453 results = append(results, data.Notes[0])
454 }
455 }
456 return db.NotesData{TeamNumber: requestedTeam, Notes: results}, nil
457}
458
459func (database *MockDatabase) AddNotes(data db.NotesData) error {
460 database.notes = append(database.notes, data)
461 return nil
462}
463
Philipp Schraderd3fac192022-03-02 20:35:46 -0800464// Returns an empty match list from the fake The Blue Alliance scraping.
465func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
466 return nil, nil
467}