go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/logdog/appengine/coordinator/flex/logs/http_test.go (about) 1 // Copyright 2018 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 logs 16 17 import ( 18 "bytes" 19 "fmt" 20 "html/template" 21 "net/http/httptest" 22 "strings" 23 "testing" 24 "time" 25 26 "go.chromium.org/luci/common/clock/testclock" 27 "go.chromium.org/luci/common/errors" 28 29 "go.chromium.org/luci/logdog/api/logpb" 30 ct "go.chromium.org/luci/logdog/appengine/coordinator/coordinatorTest" 31 32 . "github.com/smartystreets/goconvey/convey" 33 ) 34 35 func TestHTTP(t *testing.T) { 36 t.Parallel() 37 38 Convey(`With a testing configuration`, t, func() { 39 c, env := ct.Install() 40 c, tc := testclock.UseTime(c, testclock.TestRecentTimeUTC) 41 42 const project = "proj-foo" 43 const realm = "some-realm" 44 45 env.AddProject(c, project) 46 env.ActAsReader(project, realm) 47 48 tls := ct.MakeStream(c, project, realm, "testing/+/foo/bar") 49 So(tls.Put(c), ShouldBeNil) 50 51 resp := httptest.NewRecorder() 52 var options userOptions 53 54 fakeData := func(data []logResp) logData { 55 ch := make(chan logResp) 56 result := logData{ 57 ch: ch, 58 options: options, 59 logDesc: tls.Desc, 60 } 61 go func() { 62 defer close(ch) 63 for _, item := range data { 64 ch <- item 65 } 66 }() 67 return result 68 } 69 70 Convey(`Do nothing`, func() { 71 data := fakeData([]logResp{}) 72 So(serve(c, data, resp), ShouldBeNil) 73 }) 74 75 Convey(`Single Log raw`, func() { 76 options.format = "raw" 77 data := fakeData([]logResp{ 78 {desc: tls.Desc, log: tls.LogEntry(c, 0)}, 79 }) 80 So(serve(c, data, resp), ShouldBeNil) 81 So(resp.Body.String(), ShouldResemble, "log entry #0\n") 82 // Note: It's not helpful to assert HTTP header values here, 83 // because this tests serve(), which doesn't set HTTP headers. 84 }) 85 86 Convey(`Single Log full HTML`, func() { 87 options.format = "full" 88 l1 := tls.LogEntry(c, 0) 89 tc.Add(time.Minute) 90 l2 := tls.LogEntry(c, 1) 91 data := fakeData([]logResp{ 92 {desc: tls.Desc, log: l1}, 93 {desc: tls.Desc, log: l2}, 94 }) 95 So(serve(c, data, resp), ShouldBeNil) 96 body := resp.Body.String() 97 body = strings.Replace(body, "\n", "", -1) 98 body = strings.Replace(body, "\t", "", -1) 99 So(body, ShouldContainSubstring, `<div class="line" id="L0_0">`) 100 So(body, ShouldContainSubstring, `data-timestamp="1454472306000"`) 101 So(body, ShouldContainSubstring, `log entry #1`) 102 }) 103 104 Convey(`Single error, full HTML`, func() { 105 options.format = "full" 106 msg := "error encountered <script>alert()</script>" 107 data := fakeData([]logResp{ 108 {err: errors.New(msg)}, 109 }) 110 So(serve(c, data, resp).Error(), ShouldResemble, msg) 111 body := resp.Body.String() 112 body = strings.Replace(body, "\n", "", -1) 113 body = strings.Replace(body, "\t", "", -1) 114 // Note: HTML escapes don't show up in the GoConvey web interface. 115 So(body, ShouldEqual, fmt.Sprintf(`<div class="error line">LOGDOG ERROR: %s</div>`, template.HTMLEscapeString(msg))) 116 }) 117 }) 118 119 } 120 121 func TestHelperFunctions(t *testing.T) { 122 t.Parallel() 123 124 Convey(`linkify changes URLs to HTML links`, t, func() { 125 Convey(`with nothing that looks like a URL`, func() { 126 So(linkify(""), ShouldEqual, template.HTML("")) 127 So(linkify(" foo "), ShouldEqual, template.HTML(" foo ")) 128 }) 129 130 Convey(`does normal HTML escaping`, func() { 131 So(linkify("<foo>"), ShouldEqual, template.HTML("<foo>")) 132 }) 133 134 Convey(`with invalid URLs`, func() { 135 So(linkify("example.com"), ShouldEqual, template.HTML("example.com")) 136 So(linkify("http: //example.com"), ShouldEqual, template.HTML("http: //example.com")) 137 So(linkify("ftp://example.com/foo"), ShouldEqual, template.HTML("ftp://example.com/foo")) 138 So(linkify("xhttp://example.com/"), ShouldEqual, template.HTML("xhttp://example.com/")) 139 So(linkify("http://ex~ample.com"), ShouldEqual, template.HTML("http://ex~ample.com")) 140 }) 141 142 Convey(`with single simple URLs`, func() { 143 So(linkify("http://example.com"), ShouldEqual, 144 template.HTML(`<a href="http://example.com">http://example.com</a>`)) 145 So(linkify("https://example2.com"), ShouldEqual, 146 template.HTML(`<a href="https://example2.com">https://example2.com</a>`)) 147 So(linkify("https://example.com/$foo"), ShouldEqual, 148 template.HTML(`<a href="https://example.com/$foo">https://example.com/$foo</a>`)) 149 So(linkify("https://example.com/5%20%22/#x x"), ShouldEqual, 150 template.HTML(`<a href="https://example.com/5%20%22/#x">https://example.com/5%20%22/#x</a> x`)) 151 So(linkify("https://example.com.:443"), ShouldEqual, 152 template.HTML(`<a href="https://example.com.:443">https://example.com.:443</a>`)) 153 }) 154 155 Convey(`with single URLs that have " and & in the path part`, func() { 156 So(linkify("https://example.com/\"/x"), ShouldEqual, 157 template.HTML(`<a href="https://example.com/%22/x">https://example.com/"/x</a>`)) 158 So(linkify("https://example.com/&/x"), ShouldEqual, 159 template.HTML(`<a href="https://example.com/&/x">https://example.com/&/x</a>`)) 160 }) 161 162 Convey(`with single URLs that have " and <> immediately after the domain`, func() { 163 So(linkify(`https://example.com"lol</a>CRAFTED_HTML`), ShouldEqual, 164 template.HTML(`<a href="https://example.com">https://example.com</a>"lol</a>CRAFTED_HTML`)) 165 }) 166 167 Convey(`with single URLs that have " and <> in the path part`, func() { 168 So(linkify(`https://example.com/"lol</a>CRAFTED_HTML`), ShouldEqual, 169 template.HTML(`<a href="https://example.com/%22lol%3c/a%3eCRAFTED_HTML">https://example.com/"lol</a>CRAFTED_HTML</a>`)) 170 }) 171 172 Convey(`with multiple URLs`, func() { 173 So(linkify("x http://x.com z http://y.com y"), ShouldEqual, 174 template.HTML(`x <a href="http://x.com">http://x.com</a> z <a href="http://y.com">http://y.com</a> y`)) 175 So(linkify("http://x.com http://y.com"), ShouldEqual, 176 template.HTML(`<a href="http://x.com">http://x.com</a> <a href="http://y.com">http://y.com</a>`)) 177 }) 178 179 Convey(`lineTemplate uses linkify`, func() { 180 lt := logLineStruct{Text: "See https://crbug.com/1167332."} 181 w := bytes.NewBuffer([]byte{}) 182 So(lineTemplate.Execute(w, lt), ShouldBeNil) 183 So(w.String(), ShouldContainSubstring, 184 `<span class="text">See <a href="https://crbug.com/1167332">https://crbug.com/1167332</a>.</span>`) 185 }) 186 187 }) 188 189 Convey(`contentTypeHeader adjusts based on format and data content type`, t, func() { 190 // Using incomplete logData as the headers only depend on metadata, not 191 // actual log responses. This is a small unit test only testing one 192 // small behavior which is not covered by the larger serve() tests 193 // above. 194 195 Convey(`Raw text with default text encoding`, func() { 196 So(contentTypeHeader(logData{ 197 options: userOptions{format: "raw"}, 198 logDesc: &logpb.LogStreamDescriptor{ContentType: "text/plain; charset=utf-8"}, 199 }), ShouldEqual, "text/plain; charset=utf-8") 200 }) 201 202 Convey(`Raw text with alternate text encoding`, func() { 203 So(contentTypeHeader(logData{ 204 options: userOptions{format: "raw"}, 205 logDesc: &logpb.LogStreamDescriptor{ContentType: "text/plain; charset=iso-unicorns"}, 206 }), ShouldEqual, "text/plain; charset=iso-unicorns") 207 }) 208 209 Convey(`HTML with default text encoding`, func() { 210 So(contentTypeHeader(logData{ 211 options: userOptions{format: "full"}, 212 logDesc: &logpb.LogStreamDescriptor{ContentType: "text/plain; charset=utf-8"}, 213 }), ShouldEqual, "text/html; charset=utf-8") 214 }) 215 216 Convey(`HTML with alternate text encoding`, func() { 217 So(contentTypeHeader(logData{ 218 options: userOptions{format: "full"}, 219 logDesc: &logpb.LogStreamDescriptor{ContentType: "text/plain; charset=iso-unicorns"}, 220 }), ShouldEqual, "text/html; charset=iso-unicorns") 221 }) 222 }) 223 }