go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/encryptedcookies/internal/endpoint_test.go (about) 1 // Copyright 2021 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package internal 16 17 import ( 18 "context" 19 "encoding/json" 20 "net/http" 21 "net/http/httptest" 22 "testing" 23 "time" 24 25 "go.chromium.org/luci/common/clock/testclock" 26 "go.chromium.org/luci/common/retry/transient" 27 28 "go.chromium.org/luci/server/auth/authtest" 29 "go.chromium.org/luci/server/auth/openid" 30 "go.chromium.org/luci/server/encryptedcookies/session/sessionpb" 31 32 . "github.com/smartystreets/goconvey/convey" 33 . "go.chromium.org/luci/common/testing/assertions" 34 ) 35 36 func TestTokenEndpoint(t *testing.T) { 37 t.Parallel() 38 39 Convey("With fake endpoint", t, func() { 40 ctx := context.Background() 41 ctx, _ = testclock.UseTime(ctx, testclock.TestTimeUTC) 42 ctx = authtest.MockAuthConfig(ctx) 43 44 type mockedResponse struct { 45 status int 46 body []byte 47 } 48 resp := make(chan mockedResponse, 1) 49 50 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 51 if r.Method != "POST" { 52 http.Error(w, "Not a POST", 400) 53 return 54 } 55 if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" { 56 http.Error(w, "Bad content type", 400) 57 return 58 } 59 r.ParseForm() 60 if r.PostForm.Get("k1") != "v1" || r.PostForm.Get("k2") != "v2" { 61 http.Error(w, "Wrong POST body", 400) 62 return 63 } 64 mockedResp := <-resp 65 w.WriteHeader(mockedResp.status) 66 w.Write(mockedResp.body) 67 })) 68 defer ts.Close() 69 doc := openid.DiscoveryDoc{TokenEndpoint: ts.URL} 70 71 mockResponse := func(status int, body any) { 72 var blob []byte 73 if str, ok := body.(string); ok { 74 blob = []byte(str) 75 } else { 76 blob, _ = json.Marshal(body) 77 } 78 resp <- mockedResponse{status, blob} 79 } 80 81 call := func() (*sessionpb.Private, time.Time, error) { 82 return HitTokenEndpoint(ctx, &doc, map[string]string{ 83 "k1": "v1", 84 "k2": "v2", 85 }) 86 } 87 88 Convey("Happy path", func() { 89 mockResponse(200, map[string]any{ 90 "access_token": "access_token", 91 "refresh_token": "refresh_token", 92 "id_token": "id_token", 93 "expires_in": 3600, 94 }) 95 priv, exp, err := call() 96 So(err, ShouldBeNil) 97 So(priv, ShouldResembleProto, &sessionpb.Private{ 98 AccessToken: "access_token", 99 RefreshToken: "refresh_token", 100 IdToken: "id_token", 101 }) 102 So(exp.Equal(testclock.TestTimeUTC.Add(time.Hour)), ShouldBeTrue) 103 }) 104 105 Convey("Fatal err", func() { 106 mockResponse(400, "Boom") 107 _, _, err := call() 108 So(err, ShouldErrLike, `got HTTP 400`) 109 So(err, ShouldErrLike, `with body "Boom"`) 110 So(transient.Tag.In(err), ShouldBeFalse) 111 }) 112 113 Convey("Transient err", func() { 114 mockResponse(500, "Boom") 115 _, _, err := call() 116 So(err, ShouldErrLike, `got HTTP 500`) 117 So(err, ShouldErrLike, `with body "Boom"`) 118 So(transient.Tag.In(err), ShouldBeTrue) 119 }) 120 }) 121 }