-
Notifications
You must be signed in to change notification settings - Fork 42
/
assert.go
202 lines (165 loc) · 7.19 KB
/
assert.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
package isutrain
import (
"context"
"net/http"
"time"
"github.com/chibiegg/isucon9-final/bench/internal/bencherror"
"go.uber.org/zap"
"golang.org/x/sync/errgroup"
)
// 列車検索
func assertSearchTrains(ctx context.Context, endpointPath string, resp SearchTrainsResponse) error {
if resp == nil {
return bencherror.NewSimpleCriticalError("GET %s: レスポンスが空です", endpointPath)
}
if len(resp) == 0 {
return bencherror.NewSimpleCriticalError("GET %s: 列車が1件もヒットしませんでした", endpointPath)
}
for _, train := range resp {
if ok := IsValidTrainClass(train.Class); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 列車種別が不正です: %s", endpointPath, train.Class)
}
if ok := IsValidStation(train.Start); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 不正な駅です: %s", endpointPath, train.Start)
}
if ok := IsValidStation(train.Last); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 不正な駅です: %s", endpointPath, train.Last)
}
if ok := IsValidStation(train.Departure); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 不正な駅です: %s", endpointPath, train.Departure)
}
if ok := IsValidStation(train.Arrival); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 不正な駅です: %s", endpointPath, train.Arrival)
}
}
return nil
}
// 座席検索
func assertSearchTrainSeats(ctx context.Context, endpointPath string, resp *SearchTrainSeatsResponse) error {
if resp == nil {
return bencherror.NewSimpleCriticalError("GET %s: レスポンスが空です", endpointPath)
}
if _, err := time.Parse("2006/01/02", resp.Date); err != nil {
return bencherror.NewSimpleCriticalError("GET %s: Dateの形式が不正です: %s", endpointPath, resp.Date)
}
if ok := IsValidTrainClass(resp.TrainClass); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 列車種別が不正です: %s", endpointPath, resp.TrainClass)
}
if ok := IsValidCarNumber(resp.CarNumber); !ok {
return bencherror.NewSimpleCriticalError("GET %s: 車両番号が不正です: %d", endpointPath, resp.CarNumber)
}
// 席が重複して返されたら失格
dedup := map[TrainSeat]struct{}{}
for _, seat := range resp.Seats {
if _, ok := dedup[*seat]; ok {
return bencherror.NewSimpleCriticalError("GET %s: 同じ座席がレスポンスに含まれています", endpointPath)
}
dedup[*seat] = struct{}{}
}
return nil
}
// 予約
func assertReserve(ctx context.Context, endpointPath string, client *Client, req *ReserveRequest, resp *ReserveResponse) error {
lgr := zap.S()
if resp == nil {
return bencherror.NewSimpleCriticalError("POST %s: レスポンスが不正です: %+v", endpointPath, resp)
}
cache, ok := ReservationCache.Reservation(resp.ReservationID)
if !ok {
bencherror.SystemErrs.AddError(bencherror.NewSimpleCriticalError("POST %s: 予約キャッシュの取得に失敗: ReservationID=%d", endpointPath, resp.ReservationID))
return nil
}
amount, err := cache.Amount()
if err != nil {
bencherror.SystemErrs.AddError(bencherror.NewSimpleCriticalError("POST %s: 予約のamount取得に失敗: resp.IsOK=%v, resp.ReservationID=%d, resp.Amount=%d", endpointPath, resp.IsOk, resp.ReservationID, resp.Amount))
return nil
}
// レスポンスに含まれるamountは正しいか
if resp.Amount != amount {
lgr.Warnf("amountが不正になった!? reservationID=%d, want=%d, got=%d", resp.ReservationID, amount, resp.Amount)
return bencherror.NewSimpleCriticalError("POST %s: 予約 %dの amountが不正です: trainClass=%s,seatClass=%s,date=%s: want=%d, got=%d", endpointPath, resp.ReservationID, req.TrainClass, req.SeatClass, req.Date, amount, resp.Amount)
}
reserveGrp := &errgroup.Group{}
// 予約一覧に正しい情報が表示されているか
reserveGrp.Go(func() error {
reservations, err := client.ListReservations(ctx)
if err != nil {
return err
}
for _, reservation := range reservations {
if reservation.ReservationID == resp.ReservationID {
if amount != reservation.Amount {
return bencherror.NewSimpleCriticalError("POST %s: 予約 %dの amountが不正です: trainClass=%s,seatClass=%s,date=%s: want=%d, got=%d", endpointPath, reservation.ReservationID, req.TrainClass, req.SeatClass, req.Date, amount, reservation.Amount)
}
return nil
}
}
return bencherror.NewSimpleCriticalError("POST %s: 予約した内容を予約一覧画面で確認できませんでした", endpointPath)
})
// 予約確認できるか
reserveGrp.Go(func() error {
reservation, err := client.ShowReservation(ctx, resp.ReservationID)
if err != nil {
return bencherror.NewCriticalError(err, "POST %s: 予約した内容を予約確認画面で確認できませんでした", endpointPath)
}
if len(reservation.Seats) != cache.SeatCount() {
return bencherror.NewSimpleCriticalError("POST %s: 予約 %dの 座席が期待数確保できていません: want=%d, got=%d", cache.SeatCount(), len(reservation.Seats))
}
if amount != reservation.Amount {
return bencherror.NewSimpleCriticalError("POST %s: 予約 %dの amountが一致しません: want=%d, got=%d", endpointPath, reservation.ReservationID, amount, reservation.Amount)
}
return nil
})
if err := reserveGrp.Wait(); err != nil {
return err
}
return nil
}
func assertCanReserve(ctx context.Context, endpointPath string, req *ReserveRequest, resp *ReserveResponse) error {
lgr := zap.S()
canReserve, err := ReservationCache.CanReserve(req)
if err != nil {
bencherror.SystemErrs.AddError(bencherror.NewCriticalError(err, "POST %s: 予約可能判定でエラーが発生しました", endpointPath))
return nil
}
if !canReserve {
lgr.Warnw("予約できないはず",
"departure", req.Departure,
"arrival", req.Arrival,
"date", req.Date,
"train_class", req.TrainClass,
"train_name", req.TrainName,
"car_num", req.CarNum,
"seats", req.Seats,
)
return bencherror.NewSimpleCriticalError("POST %s: 予約できないはずの条件で予約が成功しました", endpointPath)
}
return nil
}
// 予約コミット
func assertCommitReservation(ctx context.Context, endpointPath string, resp *CommitReservationResponse) error {
if resp == nil {
return bencherror.NewSimpleCriticalError("POST %s: レスポンスが空です", endpointPath)
}
return nil
}
// 予約キャンセル
func assertCancelReservation(ctx context.Context, endpointPath string, client *Client, reservationID int, resp *CancelReservationResponse) error {
if resp == nil {
return bencherror.NewSimpleCriticalError("POST %s: レスポンスが空です", endpointPath)
}
reservations, err := client.ListReservations(ctx)
if err != nil {
return err
}
for _, reservation := range reservations {
if reservation.ReservationID == reservationID {
return bencherror.NewSimpleCriticalError("POST %s: キャンセルされた予約が、予約一覧に列挙されています: %d", endpointPath, reservationID)
}
}
_, err = client.ShowReservation(ctx, reservationID, StatusCodeOpt(http.StatusNotFound))
if err != nil {
return bencherror.NewSimpleCriticalError("POST %s: キャンセルされた予約が取得可能です ReservationID=%d", endpointPath, reservationID)
}
return nil
}