blob: 60bee0ec1149d4e872b614257fb30b09bf692331 [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,
225 },
226 {
227 TeamNumber: 972, MatchNumber: 1,
228 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
229 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
230 PlayedDefense: 8, Climbing: 9,
231 },
232 },
233 }
234 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800235 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderacf96232022-03-01 22:03:30 -0800236 scoutingServer.Start(8080)
237 defer scoutingServer.Stop()
238
239 builder := flatbuffers.NewBuilder(1024)
240 builder.Finish((&request_data_scouting.RequestDataScoutingT{}).Pack(builder))
241
242 response, err := debug.RequestDataScouting("http://localhost:8080", builder.FinishedBytes())
243 if err != nil {
244 t.Fatal("Failed to request all matches: ", err)
245 }
246
247 expected := request_data_scouting_response.RequestDataScoutingResponseT{
248 StatsList: []*request_data_scouting_response.StatsT{
249 // Team, Match,
250 // MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
251 // MissedShotsTele, UpperGoalTele, LowerGoalTele,
252 // DefenseRating, Climbing,
253 {
254 971, 1,
255 4, 5, 6,
256 1, 2, 3,
257 7, 8,
258 },
259 {
260 972, 1,
261 5, 6, 7,
262 2, 3, 4,
263 8, 9,
264 },
265 },
266 }
267 if len(expected.StatsList) != len(response.StatsList) {
268 t.Fatal("Expected ", expected, ", but got ", *response)
269 }
270 for i, match := range expected.StatsList {
271 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
272 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
273 }
274 }
275}
276
Philipp Schraderd3fac192022-03-02 20:35:46 -0800277// Validates that we can download the schedule from The Blue Alliance.
278func TestRefreshMatchList(t *testing.T) {
279 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
280 return []scraping.Match{
281 {
282 CompLevel: "qual",
283 MatchNumber: 1,
284 Alliances: scraping.Alliances{
285 Red: scraping.Alliance{
286 TeamKeys: []string{
287 "100",
288 "200",
289 "300",
290 },
291 },
292 Blue: scraping.Alliance{
293 TeamKeys: []string{
294 "101",
295 "201",
296 "301",
297 },
298 },
299 },
300 WinningAlliance: "",
301 EventKey: "",
302 Time: 0,
303 PredictedTime: 0,
304 ActualTime: 0,
305 PostResultTime: 0,
306 ScoreBreakdowns: scraping.ScoreBreakdowns{},
307 },
308 }, nil
309 }
310
311 database := MockDatabase{}
312 scoutingServer := server.NewScoutingServer()
313 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
314 scoutingServer.Start(8080)
315 defer scoutingServer.Stop()
316
317 builder := flatbuffers.NewBuilder(1024)
318 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
319
320 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
321 if err != nil {
322 t.Fatal("Failed to request all matches: ", err)
323 }
324
325 // Validate the response.
326 expected := refresh_match_list_response.RefreshMatchListResponseT{}
327 if !reflect.DeepEqual(expected, *response) {
328 t.Fatal("Expected ", expected, ", but got ", *response)
329 }
330
331 // Make sure that the data made it into the database.
332 expectedMatches := []db.Match{
333 {
334 MatchNumber: 1,
335 Round: 1,
336 CompLevel: "qual",
337 R1: 100,
338 R2: 200,
339 R3: 300,
340 B1: 101,
341 B2: 201,
342 B3: 301,
343 },
344 }
345 if !reflect.DeepEqual(expectedMatches, database.matches) {
346 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
347 }
348}
349
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800350// A mocked database we can use for testing. Add functionality to this as
351// needed for your tests.
352
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800353type MockDatabase struct {
354 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800355 stats []db.Stats
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800356}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800357
Philipp Schraderd3fac192022-03-02 20:35:46 -0800358func (database *MockDatabase) AddToMatch(match db.Match) error {
359 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800360 return nil
361}
362
Philipp Schrader30005e42022-03-06 13:53:58 -0800363func (database *MockDatabase) AddToStats(stats db.Stats) error {
364 database.stats = append(database.stats, stats)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800365 return nil
366}
367
368func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800369 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800370}
371
372func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800373 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800374}
375
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800376func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
377 var matches []db.Match
378 for _, match := range database.matches {
379 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
380 if team == requestedTeam {
381 matches = append(matches, match)
382 break
383 }
384 }
385 }
386 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800387}
388
389func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
390 return []db.Stats{}, nil
391}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800392
393// Returns an empty match list from the fake The Blue Alliance scraping.
394func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
395 return nil, nil
396}