blob: 999e955ca5b8bb293d5cbcf9f54d33260fa0e1c9 [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"
23 _ "github.com/frc971/971-Robot-Code/scouting/webserver/requests/messages/submit_data_scouting_response"
24 "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
95 resp, err := http.Post("http://localhost:8080/requests/submit/data_scouting", "application/octet-stream", bytes.NewReader(builder.FinishedBytes()))
96 if err != nil {
97 t.Fatalf("Failed to send request: %v", err)
98 }
99 if resp.StatusCode != http.StatusNotImplemented {
100 t.Fatal("Unexpected status code. Got", resp.Status)
101 }
102 // TODO(phil): We have nothing to validate yet. Fix that.
Philipp Schraderd9096a32022-02-24 17:53:09 -0800103 // TODO(phil): Can we use scouting/webserver/requests/debug here?
Philipp Schradercdb5cfc2022-02-20 14:57:07 -0800104}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800105
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800106// Validates that we can request the full match list.
107func TestRequestAllMatches(t *testing.T) {
108 db := MockDatabase{
109 matches: []db.Match{
110 {
111 MatchNumber: 1, Round: 1, CompLevel: "qual",
112 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
113 },
114 {
115 MatchNumber: 2, Round: 1, CompLevel: "qual",
116 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
117 },
118 {
119 MatchNumber: 3, Round: 1, CompLevel: "qual",
120 R1: 7, R2: 44, R3: 602, B1: 973, B2: 402, B3: 202,
121 },
122 },
123 }
124 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800125 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800126 scoutingServer.Start(8080)
127 defer scoutingServer.Stop()
128
129 builder := flatbuffers.NewBuilder(1024)
130 builder.Finish((&request_all_matches.RequestAllMatchesT{}).Pack(builder))
131
132 response, err := debug.RequestAllMatches("http://localhost:8080", builder.FinishedBytes())
133 if err != nil {
134 t.Fatal("Failed to request all matches: ", err)
135 }
136
137 expected := request_all_matches_response.RequestAllMatchesResponseT{
138 MatchList: []*request_all_matches_response.MatchT{
139 // MatchNumber, Round, CompLevel
140 // R1, R2, R3, B1, B2, B3
141 {
142 1, 1, "qual",
143 5, 42, 600, 971, 400, 200,
144 },
145 {
146 2, 1, "qual",
147 6, 43, 601, 972, 401, 201,
148 },
149 {
150 3, 1, "qual",
151 7, 44, 602, 973, 402, 202,
152 },
153 },
154 }
155 if len(expected.MatchList) != len(response.MatchList) {
156 t.Fatal("Expected ", expected, ", but got ", *response)
157 }
158 for i, match := range expected.MatchList {
159 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
160 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
161 }
162 }
163}
164
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800165// Validates that we can request the full match list.
166func TestRequestMatchesForTeam(t *testing.T) {
167 db := MockDatabase{
168 matches: []db.Match{
169 {
170 MatchNumber: 1, Round: 1, CompLevel: "qual",
171 R1: 5, R2: 42, R3: 600, B1: 971, B2: 400, B3: 200,
172 },
173 {
174 MatchNumber: 2, Round: 1, CompLevel: "qual",
175 R1: 6, R2: 43, R3: 601, B1: 972, B2: 401, B3: 201,
176 },
177 },
178 }
179 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800180 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800181 scoutingServer.Start(8080)
182 defer scoutingServer.Stop()
183
184 builder := flatbuffers.NewBuilder(1024)
185 builder.Finish((&request_matches_for_team.RequestMatchesForTeamT{
186 Team: 971,
187 }).Pack(builder))
188
189 response, err := debug.RequestMatchesForTeam("http://localhost:8080", builder.FinishedBytes())
190 if err != nil {
191 t.Fatal("Failed to request all matches: ", err)
192 }
193
194 expected := request_matches_for_team_response.RequestMatchesForTeamResponseT{
195 MatchList: []*request_matches_for_team_response.MatchT{
196 // MatchNumber, Round, CompLevel
197 // R1, R2, R3, B1, B2, B3
198 {
199 1, 1, "qual",
200 5, 42, 600, 971, 400, 200,
201 },
202 },
203 }
204 if len(expected.MatchList) != len(response.MatchList) {
205 t.Fatal("Expected ", expected, ", but got ", *response)
206 }
207 for i, match := range expected.MatchList {
208 if !reflect.DeepEqual(*match, *response.MatchList[i]) {
209 t.Fatal("Expected for match", i, ":", *match, ", but got:", *response.MatchList[i])
210 }
211 }
212}
213
Philipp Schraderacf96232022-03-01 22:03:30 -0800214// Validates that we can request the stats.
215func TestRequestDataScouting(t *testing.T) {
216 db := MockDatabase{
217 stats: []db.Stats{
218 {
219 TeamNumber: 971, MatchNumber: 1,
220 ShotsMissed: 1, UpperGoalShots: 2, LowerGoalShots: 3,
221 ShotsMissedAuto: 4, UpperGoalAuto: 5, LowerGoalAuto: 6,
222 PlayedDefense: 7, Climbing: 8,
223 },
224 {
225 TeamNumber: 972, MatchNumber: 1,
226 ShotsMissed: 2, UpperGoalShots: 3, LowerGoalShots: 4,
227 ShotsMissedAuto: 5, UpperGoalAuto: 6, LowerGoalAuto: 7,
228 PlayedDefense: 8, Climbing: 9,
229 },
230 },
231 }
232 scoutingServer := server.NewScoutingServer()
Philipp Schraderd3fac192022-03-02 20:35:46 -0800233 HandleRequests(&db, scrapeEmtpyMatchList, scoutingServer)
Philipp Schraderacf96232022-03-01 22:03:30 -0800234 scoutingServer.Start(8080)
235 defer scoutingServer.Stop()
236
237 builder := flatbuffers.NewBuilder(1024)
238 builder.Finish((&request_data_scouting.RequestDataScoutingT{}).Pack(builder))
239
240 response, err := debug.RequestDataScouting("http://localhost:8080", builder.FinishedBytes())
241 if err != nil {
242 t.Fatal("Failed to request all matches: ", err)
243 }
244
245 expected := request_data_scouting_response.RequestDataScoutingResponseT{
246 StatsList: []*request_data_scouting_response.StatsT{
247 // Team, Match,
248 // MissedShotsAuto, UpperGoalAuto, LowerGoalAuto,
249 // MissedShotsTele, UpperGoalTele, LowerGoalTele,
250 // DefenseRating, Climbing,
251 {
252 971, 1,
253 4, 5, 6,
254 1, 2, 3,
255 7, 8,
256 },
257 {
258 972, 1,
259 5, 6, 7,
260 2, 3, 4,
261 8, 9,
262 },
263 },
264 }
265 if len(expected.StatsList) != len(response.StatsList) {
266 t.Fatal("Expected ", expected, ", but got ", *response)
267 }
268 for i, match := range expected.StatsList {
269 if !reflect.DeepEqual(*match, *response.StatsList[i]) {
270 t.Fatal("Expected for stats", i, ":", *match, ", but got:", *response.StatsList[i])
271 }
272 }
273}
274
Philipp Schraderd3fac192022-03-02 20:35:46 -0800275// Validates that we can download the schedule from The Blue Alliance.
276func TestRefreshMatchList(t *testing.T) {
277 scrapeMockSchedule := func(int32, string) ([]scraping.Match, error) {
278 return []scraping.Match{
279 {
280 CompLevel: "qual",
281 MatchNumber: 1,
282 Alliances: scraping.Alliances{
283 Red: scraping.Alliance{
284 TeamKeys: []string{
285 "100",
286 "200",
287 "300",
288 },
289 },
290 Blue: scraping.Alliance{
291 TeamKeys: []string{
292 "101",
293 "201",
294 "301",
295 },
296 },
297 },
298 WinningAlliance: "",
299 EventKey: "",
300 Time: 0,
301 PredictedTime: 0,
302 ActualTime: 0,
303 PostResultTime: 0,
304 ScoreBreakdowns: scraping.ScoreBreakdowns{},
305 },
306 }, nil
307 }
308
309 database := MockDatabase{}
310 scoutingServer := server.NewScoutingServer()
311 HandleRequests(&database, scrapeMockSchedule, scoutingServer)
312 scoutingServer.Start(8080)
313 defer scoutingServer.Stop()
314
315 builder := flatbuffers.NewBuilder(1024)
316 builder.Finish((&refresh_match_list.RefreshMatchListT{}).Pack(builder))
317
318 response, err := debug.RefreshMatchList("http://localhost:8080", builder.FinishedBytes())
319 if err != nil {
320 t.Fatal("Failed to request all matches: ", err)
321 }
322
323 // Validate the response.
324 expected := refresh_match_list_response.RefreshMatchListResponseT{}
325 if !reflect.DeepEqual(expected, *response) {
326 t.Fatal("Expected ", expected, ", but got ", *response)
327 }
328
329 // Make sure that the data made it into the database.
330 expectedMatches := []db.Match{
331 {
332 MatchNumber: 1,
333 Round: 1,
334 CompLevel: "qual",
335 R1: 100,
336 R2: 200,
337 R3: 300,
338 B1: 101,
339 B2: 201,
340 B3: 301,
341 },
342 }
343 if !reflect.DeepEqual(expectedMatches, database.matches) {
344 t.Fatal("Expected ", expectedMatches, ", but got ", database.matches)
345 }
346}
347
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800348// A mocked database we can use for testing. Add functionality to this as
349// needed for your tests.
350
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800351type MockDatabase struct {
352 matches []db.Match
Philipp Schraderacf96232022-03-01 22:03:30 -0800353 stats []db.Stats
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800354}
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800355
Philipp Schraderd3fac192022-03-02 20:35:46 -0800356func (database *MockDatabase) AddToMatch(match db.Match) error {
357 database.matches = append(database.matches, match)
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800358 return nil
359}
360
361func (database *MockDatabase) AddToStats(db.Stats) error {
362 return nil
363}
364
365func (database *MockDatabase) ReturnMatches() ([]db.Match, error) {
Philipp Schradercbf5c6a2022-02-27 23:25:19 -0800366 return database.matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800367}
368
369func (database *MockDatabase) ReturnStats() ([]db.Stats, error) {
Philipp Schraderacf96232022-03-01 22:03:30 -0800370 return database.stats, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800371}
372
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800373func (database *MockDatabase) QueryMatches(requestedTeam int32) ([]db.Match, error) {
374 var matches []db.Match
375 for _, match := range database.matches {
376 for _, team := range []int32{match.R1, match.R2, match.R3, match.B1, match.B2, match.B3} {
377 if team == requestedTeam {
378 matches = append(matches, match)
379 break
380 }
381 }
382 }
383 return matches, nil
Philipp Schrader8747f1b2022-02-23 23:56:22 -0800384}
385
386func (database *MockDatabase) QueryStats(int) ([]db.Stats, error) {
387 return []db.Stats{}, nil
388}
Philipp Schraderd3fac192022-03-02 20:35:46 -0800389
390// Returns an empty match list from the fake The Blue Alliance scraping.
391func scrapeEmtpyMatchList(int32, string) ([]scraping.Match, error) {
392 return nil, nil
393}