From ccfb2ebac16733d72c17f61efe254c7c809b0cee Mon Sep 17 00:00:00 2001 From: Michael Bretterklieber Date: Sat, 25 May 2024 15:08:16 +0200 Subject: [PATCH] more tests, add validation --- api/location.go | 1 - api/modem.go | 1 - api/sms.go | 15 ++++-- api/sms_test.go | 123 ++++++++++++++++++++++++++++++++++++++++++++-- docs/docs.go | 38 ++------------ docs/swagger.json | 38 ++------------ docs/swagger.yaml | 26 ++-------- go.mod | 11 ++++- go.sum | 18 +++++++ 9 files changed, 172 insertions(+), 99 deletions(-) diff --git a/api/location.go b/api/location.go index e8a04ae..ba94dd1 100644 --- a/api/location.go +++ b/api/location.go @@ -11,7 +11,6 @@ import ( // @Produce json // @Tags location // @Success 200 -// @Failure 404 {object} http.ErrorResponse // @Router /location [get] // @Param modem query string false "Modem-Id" func (a *Api) LocationGet(w http.ResponseWriter, r *http.Request) { diff --git a/api/modem.go b/api/modem.go index 89e5fbd..4f0332b 100644 --- a/api/modem.go +++ b/api/modem.go @@ -29,7 +29,6 @@ func (a *Api) ModemList(w http.ResponseWriter, r *http.Request) { // @Produce json // @Tags modem // @Success 200 -// @Failure 404 {object} http.ErrorResponse // @Router /modem/{id} [get] // @Param id path string true "Modem-Id" func (a *Api) ModemDetail(w http.ResponseWriter, r *http.Request) { diff --git a/api/sms.go b/api/sms.go index 568e18d..eeff8aa 100644 --- a/api/sms.go +++ b/api/sms.go @@ -5,14 +5,15 @@ import ( "encoding/json" "fmt" "github.com/go-chi/chi/v5" + "github.com/go-playground/validator/v10" utils "github.com/mbretter/go-mmcli-svr/http" "net/http" "strings" ) type SmsRequestData struct { - Number string `json:"number" example:"+431234567890"` - Text string `json:"text" example:"Ping"` + Number string `json:"number" example:"+431234567890" validate:"required,e164"` + Text string `json:"text" example:"Ping" validate:"required,max=160"` } // SmsCreated {"modem":{"messaging":{"created-sms":"/org/freedesktop/ModemManager1/SMS/0"}}} @@ -30,7 +31,6 @@ type SmsCreated struct { // @Produce json // @Tags sms // @Success 200 -// @Failure 404 {object} http.ErrorResponse // @Router /sms/ [get] // @Router /sms/{id} [get] // @Param id path string true "SMS-Id" @@ -79,6 +79,13 @@ func (a *Api) SmsSend(w http.ResponseWriter, r *http.Request) { return } + validate := validator.New() + err = validate.Struct(requestData) + if err != nil { + utils.WriteError(w, r, http.StatusBadRequest, err) + return + } + smsStr := fmt.Sprintf("number='%s',text='%s'", requestData.Number, requestData.Text) jsonBuf, err := a.backend.ExecModem(modem, "--messaging-create-sms="+smsStr) if err != nil { @@ -89,7 +96,7 @@ func (a *Api) SmsSend(w http.ResponseWriter, r *http.Request) { var smsCreated SmsCreated err = json.NewDecoder(bytes.NewReader(jsonBuf)).Decode(&smsCreated) if err != nil { - utils.WriteError(w, r, http.StatusBadRequest, err) + utils.WriteError(w, r, http.StatusInternalServerError, err) return } diff --git a/api/sms_test.go b/api/sms_test.go index f63b702..69311cc 100644 --- a/api/sms_test.go +++ b/api/sms_test.go @@ -157,10 +157,45 @@ func TestSmsSend(t *testing.T) { { "Success", "", - `{"number":"+436643544125","text":"Ping"}`, + `{"number":"+436641234567","text":"Ping"}`, http.StatusOK, `{"message":"successfully sent the SMS"}`, }, + { + "Invalid json", + "", + `{"foo":"+`, + http.StatusBadRequest, + `{"error":"unexpected EOF"}`, + }, + { + "Invalid request data", + "", + `{"foo":"+436641234567","xxx":"Ping"}`, + http.StatusBadRequest, + `{"error":"Key: 'SmsRequestData.Number' Error:Field validation for 'Number' failed on the 'required' tag\nKey: 'SmsRequestData.Text' Error:Field validation for 'Text' failed on the 'required' tag"}`, + }, + { + "Create SMS failed", + "", + `{"number":"+436641234567","text":"Ping"}`, + http.StatusInternalServerError, + `{"error":"internal server error"}`, + }, + { + "Create SMS invalid json", + "", + `{"number":"+436641234567","text":"Ping"}`, + http.StatusInternalServerError, + `{"error":"internal server error"}`, + }, + { + "Send SMS failed", + "", + `{"number":"+436641234567","text":"Ping"}`, + http.StatusInternalServerError, + `{"error":"internal server error"}`, + }, } for _, tt := range tests { @@ -173,8 +208,18 @@ func TestSmsSend(t *testing.T) { r := newRequest(tt.requestBody, tt.modemId, logger) w := httptest.NewRecorder() - backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-create-sms=number='+436643544125',text='Ping'").Return([]byte(`{"modem":{"messaging":{"created-sms":"/org/freedesktop/ModemManager1/SMS/0"}}}`), nil) - backendMock.EXPECT().Exec("-s", "/org/freedesktop/ModemManager1/SMS/0", "--send").Return([]byte(`successfully sent the SMS`), nil) + if tt.name == "Success" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-create-sms=number='+436641234567',text='Ping'").Return([]byte(`{"modem":{"messaging":{"created-sms":"/org/freedesktop/ModemManager1/SMS/0"}}}`), nil) + backendMock.EXPECT().Exec("-s", "/org/freedesktop/ModemManager1/SMS/0", "--send").Return([]byte(`successfully sent the SMS`), nil) + } else if tt.name == "Create SMS failed" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-create-sms=number='+436641234567',text='Ping'").Return([]byte(`create SMS failed`), errors.New("failed")) + } else if tt.name == "Create SMS invalid json" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-create-sms=number='+436641234567',text='Ping'").Return([]byte(`{"m`), nil) + } else if tt.name == "Send SMS failed" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-create-sms=number='+436641234567',text='Ping'").Return([]byte(`{"modem":{"messaging":{"created-sms":"/org/freedesktop/ModemManager1/SMS/0"}}}`), nil) + backendMock.EXPECT().Exec("-s", "/org/freedesktop/ModemManager1/SMS/0", "--send").Return([]byte(`failed`), errors.New("failed")) + } + api.SmsSend(w, r) resp := w.Result() body, _ := io.ReadAll(resp.Body) @@ -184,3 +229,75 @@ func TestSmsSend(t *testing.T) { }) } } + +func TestSmsDelete(t *testing.T) { + newRequest := func(smsId string, modemId string, logger *slog.Logger) *http.Request { + r := httptest.NewRequest("DELETE", "http://127.0.0.1:8743/sms/", nil) + + ctx := context.WithValue(r.Context(), "logger", logger) + ctx = context.WithValue(ctx, "modem", modemId) + + if len(smsId) > 0 { + routeContext := chi.NewRouteContext() + routeContext.URLParams.Add("id", smsId) + ctx = context.WithValue(ctx, chi.RouteCtxKey, routeContext) + } + + return r.WithContext(ctx) + } + + tests := []struct { + name string + modemId string + smsId string + expectedStatusCode int + expectedBody string + }{ + { + "Success", + "1", + "/org/freedesktop/ModemManager1/SMS/0", + http.StatusOK, + `{"message":"successfully deleted SMS from modem"}`, + }, + { + "Invalid SMS id", + "", + "&#'", + http.StatusBadRequest, + `{"error":"invalid ID"}`, + }, + { + "Delete failed", + "", + "12", + http.StatusInternalServerError, + `{"error":"internal server error"}`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + api, backendMock := ProvideTestApi(t) + + var buff bytes.Buffer + logger := slog.New(slog.NewTextHandler(&buff, nil)) + + r := newRequest(tt.smsId, tt.modemId, logger) + w := httptest.NewRecorder() + + if tt.name == "Success" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-delete-sms="+tt.smsId).Return([]byte("successfully deleted SMS from modem"), nil) + } else if tt.name == "Delete failed" { + backendMock.EXPECT().ExecModem(tt.modemId, "--messaging-delete-sms="+tt.smsId).Return([]byte("failed"), errors.New("failed")) + } + + api.SmsDelete(w, r) + resp := w.Result() + body, _ := io.ReadAll(resp.Body) + + assert.Equal(t, tt.expectedStatusCode, resp.StatusCode) + assert.Equal(t, tt.expectedBody, strings.Trim(string(body), " \n")) + }) + } +} diff --git a/docs/docs.go b/docs/docs.go index 10d9a58..b6367f5 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -41,12 +41,6 @@ const docTemplate = `{ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -94,12 +88,6 @@ const docTemplate = `{ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -190,12 +178,6 @@ const docTemplate = `{ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -230,12 +212,6 @@ const docTemplate = `{ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } }, @@ -270,6 +246,10 @@ const docTemplate = `{ "definitions": { "api.SmsRequestData": { "type": "object", + "required": [ + "number", + "text" + ], "properties": { "number": { "type": "string", @@ -277,18 +257,10 @@ const docTemplate = `{ }, "text": { "type": "string", + "maxLength": 160, "example": "Ping" } } - }, - "http.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "an error occurred" - } - } } }, "securityDefinitions": { diff --git a/docs/swagger.json b/docs/swagger.json index 519edae..0b4d706 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -34,12 +34,6 @@ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -87,12 +81,6 @@ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -183,12 +171,6 @@ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } } @@ -223,12 +205,6 @@ "responses": { "200": { "description": "OK" - }, - "404": { - "description": "Not Found", - "schema": { - "$ref": "#/definitions/http.ErrorResponse" - } } } }, @@ -263,6 +239,10 @@ "definitions": { "api.SmsRequestData": { "type": "object", + "required": [ + "number", + "text" + ], "properties": { "number": { "type": "string", @@ -270,18 +250,10 @@ }, "text": { "type": "string", + "maxLength": 160, "example": "Ping" } } - }, - "http.ErrorResponse": { - "type": "object", - "properties": { - "error": { - "type": "string", - "example": "an error occurred" - } - } } }, "securityDefinitions": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 84d1df2..5424ba4 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -6,13 +6,11 @@ definitions: type: string text: example: Ping + maxLength: 160 type: string - type: object - http.ErrorResponse: - properties: - error: - example: an error occurred - type: string + required: + - number + - text type: object host: 127.0.0.1:8743 info: @@ -37,10 +35,6 @@ paths: responses: "200": description: OK - "404": - description: Not Found - schema: - $ref: '#/definitions/http.ErrorResponse' summary: Get location tags: - location @@ -71,10 +65,6 @@ paths: responses: "200": description: OK - "404": - description: Not Found - schema: - $ref: '#/definitions/http.ErrorResponse' summary: Modem info tags: - modem @@ -131,10 +121,6 @@ paths: responses: "200": description: OK - "404": - description: Not Found - schema: - $ref: '#/definitions/http.ErrorResponse' summary: List SMS messages tags: - sms @@ -174,10 +160,6 @@ paths: responses: "200": description: OK - "404": - description: Not Found - schema: - $ref: '#/definitions/http.ErrorResponse' summary: List SMS messages tags: - sms diff --git a/go.mod b/go.mod index af51e2a..e5d96bd 100644 --- a/go.mod +++ b/go.mod @@ -14,17 +14,24 @@ require ( require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/josharian/intern v1.0.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.6 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/swaggo/files v1.0.1 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect + golang.org/x/crypto v0.19.0 // indirect + golang.org/x/net v0.21.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.7.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index ddaca7b..b3b0cc4 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s= github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= @@ -18,6 +20,12 @@ github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -27,6 +35,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= @@ -51,6 +61,8 @@ github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= @@ -60,6 +72,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -70,6 +84,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -77,6 +93,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=