blob: c716b448c91f6d5534bb3eb6244ade57b99e4add [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,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700227 CollectedBy: "john",
Philipp Schraderacf96232022-03-01 22:03:30 -0800228 },
229 {
230 TeamNumber: 972, MatchNumber: 1,
231 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
232 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
233 PlayedDefense: 8, Climbing: 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700234 CollectedBy: "andrea",
Philipp Schraderacf96232022-03-01 22:03:30 -0800235 },
236 },
237 }
238 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800239 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderacf96232022-03-01 22:03:30 -0800240 scoutingServer.Start(8080)
241 defer scoutingServer.Stop()
242
243 builder := flatbuffers.NewBuilder(1024)
244 builder.Finish((&request_data_scouting.RequestDataScoutingT{}).Pack(builder))
245
246 response, err := debug.RequestDataScouting("http://localhost:8080", builder.FinishedBytes())
247 if err != nil {
248 t.Fatal("Failed to request all matches: ", err)
249 }
250
251 expected := request_data_scouting_response.RequestDataScoutingResponseT{
252 StatsList: []*request_data_scouting_response.StatsT{
253 // Team, Match,
254 // MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
255 // MissedShotsTele, UpperGoalTele, LowerGoalTele,
256 // DefenseRating, Climbing,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700257 // CollectedBy,
Philipp Schraderacf96232022-03-01 22:03:30 -0800258 {
259 971, 1,
260 4, 5, 6,
261 1, 2, 3,
262 7, 8,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700263 "john",
Philipp Schraderacf96232022-03-01 22:03:30 -0800264 },
265 {
266 972, 1,
267 5, 6, 7,
268 2, 3, 4,
269 8, 9,
Philipp Schraderfae8a7e2022-03-13 22:51:54 -0700270 "andrea",
Philipp Schraderacf96232022-03-01 22:03:30 -0800271 },
272 },
273 }
274 if len(expected.StatsList) != len(response.StatsList) {
275 t.Fatal("Expected ", expected, ", but got ", *response)
276 }
277 for i, match := range expected.StatsList {
278 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
279 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
280 }
281 }
282}
283
Alex Perry81f96ba2022-03-13 18:26:19 -0700284func TestSubmitNotes(t *testing.T) {
285 database := MockDatabase{}
286 scoutingServer := server.NewScoutingServer()
287 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
288 scoutingServer.Start(8080)
289 defer scoutingServer.Stop()
290
291 builder := flatbuffers.NewBuilder(1024)
292 builder.Finish((&submit_notes.SubmitNotesT{
293 Team: 971,
294 Notes: "Notes",
295 }).Pack(builder))
296
297 _, err := debug.SubmitNotes("http://localhost:8080", builder.FinishedBytes())
298 if err != nil {
299 t.Fatal("Failed to submit notes: ", err)
300 }
301
302 expected := []db.NotesData{
303 {TeamNumber: 971, Notes: []string{"Notes"}},
304 }
305
306 if !reflect.DeepEqual(database.notes, expected) {
307 t.Fatal("Submitted notes did not match", expected, database.notes)
308 }
309}
310
311func TestRequestNotes(t *testing.T) {
312 database := MockDatabase{
313 notes: []db.NotesData{{
314 TeamNumber: 971,
315 Notes: []string{"Notes"},
316 }},
317 }
318 scoutingServer := server.NewScoutingServer()
319 HandleRequests(&database, scrapeEmtpyMatchList, scoutingServer)
320 scoutingServer.Start(8080)
321 defer scoutingServer.Stop()
322
323 builder := flatbuffers.NewBuilder(1024)
324 builder.Finish((&request_notes_for_team.RequestNotesForTeamT{
325 Team: 971,
326 }).Pack(builder))
327 response, err := debug.RequestNotes("http://localhost:8080", builder.FinishedBytes())
328 if err != nil {
329 t.Fatal("Failed to submit notes: ", err)
330 }
331
332 if response.Notes[0].Data != "Notes" {
333 t.Fatal("requested notes did not match", response)
334 }
335}
336
Philipp Schraderd3fac192022-03-02 20:35:46 -0800337// Validates that we can download the schedule from The Blue Alliance.
338func TestRefreshMatchList(t *testing.T) {
339 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
340 return []scraping.Match{
341 {
342 CompLevel: "qual",
343 MatchNumber: 1,
344 Alliances: scraping.Alliances{
345 Red: scraping.Alliance{
346 TeamKeys: []string{
347 "100",
348 "200",
349 "300",
350 },
351 },
352 Blue: scraping.Alliance{
353 TeamKeys: []string{
354 "101",
355 "201",
356 "301",
357 },
358 },
359 },
360 WinningAlliance: "",
361 EventKey: "",
362 Time: 0,
363 PredictedTime: 0,
364 ActualTime: 0,
365 PostResultTime: 0,
366 ScoreBreakdowns: scraping.ScoreBreakdowns{},
367 },
368 }, nil
369 }
370
371 database := MockDatabase{}
372 scoutingServer := server.NewScoutingServer()
373 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
374 scoutingServer.Start(8080)
375 defer scoutingServer.Stop()
376
377 builder := flatbuffers.NewBuilder(1024)
378 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
379
380 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
381 if err != nil {
382 t.Fatal("Failed to request all matches: ", err)
383 }
384
385 // Validate the response.
386 expected := refresh_match_list_response.RefreshMatchListResponseT{}
387 if !reflect.DeepEqual(expected, *response) {
388 t.Fatal("Expected ", expected, ", but got ", *response)
389 }
390
391 // Make sure that the data made it into the database.
392 expectedMatches := []db.Match{
393 {
394 MatchNumber: 1,
395 Round: 1,
396 CompLevel: "qual",
397 R1: 100,
398 R2: 200,
399 R3: 300,
400 B1: 101,
401 B2: 201,
402 B3: 301,
403 },
404 }
405 if !reflect.DeepEqual(expectedMatches, database.matches) {
406 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
407 }
408}
409
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800410// A mocked database we can use for testing. Add functionality to this as
411// needed for your tests.
412
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800413type MockDatabase struct {
414 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800415 stats []db.Stats
Alex Perry81f96ba2022-03-13 18:26:19 -0700416 notes []db.NotesData
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800417}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800418
Philipp Schraderd3fac192022-03-02 20:35:46 -0800419func (database *MockDatabase) AddToMatch(match db.Match) error {
420 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800421 return nil
422}
423
Philipp Schrader30005e42022-03-06 13:53:58 -0800424func (database *MockDatabase) AddToStats(stats db.Stats) error {
425 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800426 return nil
427}
428
429func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800430 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800431}
432
433func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800434 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800435}
436
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800437func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
438 var matches []db.Match
439 for _, match := range database.matches {
440 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
441 if team == requestedTeam {
442 matches = append(matches, match)
443 break
444 }
445 }
446 }
447 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800448}
449
450func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
451 return []db.Stats{}, nil
452}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800453
Alex Perry81f96ba2022-03-13 18:26:19 -0700454func (database *MockDatabase) QueryNotes(requestedTeam int32) (db.NotesData, error) {
455 var results []string
456 for _, data := range database.notes {
457 if data.TeamNumber == requestedTeam {
458 results = append(results, data.Notes[0])
459 }
460 }
461 return db.NotesData{TeamNumber: requestedTeam, Notes: results}, nil
462}
463
464func (database *MockDatabase) AddNotes(data db.NotesData) error {
465 database.notes = append(database.notes, data)
466 return nil
467}
468
Philipp Schraderd3fac192022-03-02 20:35:46 -0800469// Returns an empty match list from the fake The Blue Alliance scraping.
470func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
471 return nil, nil
472}