github.com/cloudfoundry/cli@v7.1.0+incompatible/util/ui/request_logger_file_writer_test.go (about) 1 package ui_test 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 "time" 11 12 . "code.cloudfoundry.org/cli/util/ui" 13 14 . "github.com/onsi/ginkgo" 15 . "github.com/onsi/gomega" 16 . "github.com/onsi/gomega/gbytes" 17 ) 18 19 var _ = Describe("Request Logger File Writer", func() { 20 var ( 21 ui *UI 22 display *RequestLoggerFileWriter 23 tmpdir string 24 logFile1 string 25 logFile2 string 26 ) 27 28 BeforeEach(func() { 29 ui = NewTestUI(NewBuffer(), NewBuffer(), NewBuffer()) 30 }) 31 32 Describe("Valid file paths", func() { 33 BeforeEach(func() { 34 var err error 35 tmpdir, err = ioutil.TempDir("", "request_logger") 36 Expect(err).ToNot(HaveOccurred()) 37 38 logFile1 = filepath.Join(tmpdir, "tmp_sub_dir", "tmpfile1") 39 logFile2 = filepath.Join(tmpdir, "tmp", "sub", "dir", ".", "tmpfile2") 40 display = ui.RequestLoggerFileWriter([]string{logFile1, logFile2}) 41 Expect(display.Start()).ToNot(HaveOccurred()) 42 }) 43 44 AfterEach(func() { 45 Expect(os.RemoveAll(tmpdir)).NotTo(HaveOccurred()) 46 }) 47 48 Describe("DisplayBody", func() { 49 It("writes the redacted value", func() { 50 err := display.DisplayBody([]byte("this is a body")) 51 Expect(err).ToNot(HaveOccurred()) 52 53 err = display.Stop() 54 Expect(err).ToNot(HaveOccurred()) 55 56 contents, err := ioutil.ReadFile(logFile1) 57 Expect(err).ToNot(HaveOccurred()) 58 Expect(string(contents)).To(Equal(RedactedValue + "\n")) 59 60 contents, err = ioutil.ReadFile(logFile2) 61 Expect(err).ToNot(HaveOccurred()) 62 Expect(string(contents)).To(Equal(RedactedValue + "\n")) 63 }) 64 }) 65 66 Describe("DisplayDump", func() { 67 It("creates the intermediate dirs and writes the dump to file", func() { 68 err := display.DisplayDump("this is a dump of stuff") 69 Expect(err).ToNot(HaveOccurred()) 70 71 err = display.Stop() 72 Expect(err).ToNot(HaveOccurred()) 73 74 contents, err := ioutil.ReadFile(logFile1) 75 Expect(err).ToNot(HaveOccurred()) 76 Expect(string(contents)).To(Equal("this is a dump of stuff\n")) 77 78 contents, err = ioutil.ReadFile(logFile2) 79 Expect(err).ToNot(HaveOccurred()) 80 Expect(string(contents)).To(Equal("this is a dump of stuff\n")) 81 }) 82 83 It("redacts auth tokens", func() { 84 dump := `GET /apps/ce03a2e2-95c0-4f3b-abb9-32718d408c8b/stream HTTP/1.1 85 Host: wss://doppler.bosh-lite.com:443 86 Upgrade: websocket 87 Connection: Upgrade 88 Sec-WebSocket-Version: 13 89 Sec-WebSocket-Key: [HIDDEN] 90 Set-Cookie: please-redact-this 91 Authorization: bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImtleS0xIiwidHlwIjoiSldUIn0.eyJqdGkiOiI3YzRmYWEyZjI5MmQ0MTQ5ODM5NGE3OTU0Y2E3ZWNlMCIsInN1YiI6IjIyMjNiM2IzLTE3ZTktNDJkNi1iNzQzLThjZjcyZWIwOWRlNSIsInNjb3BlIjpbInJvdXRpbmcucm91dGVyX2dyb3Vwcy5yZWFkIiwiY2xvdWRfY29udHJvbGxlci5yZWFkIiwicGFzc3dvcmQud3JpdGUiLCJjbG91ZF9jb250cm9sbGVyLndyaXRlIiwib3BlbmlkIiwicm91dGluZy5yb3V0ZXJfZ3JvdXBzLndyaXRlIiwiZG9wcGxlci5maXJlaG9zZSIsInNjaW0ud3JpdGUiLCJzY2ltLnJlYWQiLCJjbG91ZF9jb250cm9sbGVyLmFkbWluIiwidWFhLnVzZXIiXSwiY2xpZW50X2lkIjoiY2YiLCJjaWQiOiJjZiIsImF6cCI6ImNmIiwiZ3JhbnRfdHlwZSI6InBhc3N3b3JkIiwidXNlcl9pZCI6IjIyMjNiM2IzLTE3ZTktNDJkNi1iNzQzLThjZjcyZWIwOWRlNSIsIm9yaWdpbiI6InVhYSIsInVzZXJfbmFtZSI6ImFkbWluIiwiZW1haWwiOiJhZG1pbiIsInJldl9zaWciOiI4NDBiMDBhMyIsImlhdCI6MTQ5NjQyNTU5NiwiZXhwIjoxNDk2NDI2MTk2LCJpc3MiOiJodHRwczovL3VhYS5ib3NoLWxpdGUuY29tL29hdXRoL3Rva2VuIiwiemlkIjoidWFhIiwiYXVkIjpbInNjaW0iLCJjbG91ZF9jb250cm9sbGVyIiwicGFzc3dvcmQiLCJjZiIsInVhYSIsIm9wZW5pZCIsImRvcHBsZXIiLCJyb3V0aW5nLnJvdXRlcl9ncm91cHMiXX0.TFDmHviKcs-eeNoz79dVwOl-k_dHTdqHkyztont2qnBDchNSpWvR5Yba54MMG8uTUHM72YbCopxdyaLY-g8s5wJFGLaBocrDgqswUh3mQRvynQG6_zne1h_0oHXnm0U-ZPnTyV8qjtHUoLvks4GOuktXc6ZE3NriWODpKIU5WdMgEbvyhuTnUEn88rQnmGJbKvHOIilulb6avSkZfTEq1o8w4VLCeRDlVLNh5JzCUtGzLfImNb31ks_Wv6HuI8kFjQZ5PQiTYjlhkuDQOcNSaAyWxQ_7425hiA7x8omBgEr-uST7GsxLvgoHqQaDH0JSTgMmO_GaN_QD52JVuru9og 92 Origin: wss://doppler.bosh-lite.com:443` 93 err := display.DisplayDump(dump) 94 Expect(err).ToNot(HaveOccurred()) 95 96 err = display.Stop() 97 Expect(err).ToNot(HaveOccurred()) 98 99 raw, err := ioutil.ReadFile(logFile1) 100 Expect(err).ToNot(HaveOccurred()) 101 contents := string(raw) 102 103 Expect(contents).To(MatchRegexp("Connection: Upgrade")) 104 Expect(contents).To(MatchRegexp(`Set-Cookie: \[PRIVATE DATA HIDDEN\]`)) 105 Expect(contents).To(MatchRegexp(`Authorization: \[PRIVATE DATA HIDDEN\]`)) 106 Expect(contents).To(MatchRegexp("Origin: wss://doppler.bosh-lite.com:443")) 107 108 raw, err = ioutil.ReadFile(logFile2) 109 Expect(err).ToNot(HaveOccurred()) 110 contents = string(raw) 111 112 Expect(contents).To(MatchRegexp("Connection: Upgrade")) 113 Expect(contents).To(MatchRegexp(`Authorization: \[PRIVATE DATA HIDDEN\]`)) 114 Expect(contents).To(MatchRegexp("Origin: wss://doppler.bosh-lite.com:443")) 115 }) 116 }) 117 118 Describe("DisplayHeader", func() { 119 It("writes the header key and value", func() { 120 err := display.DisplayHeader("Header", "Value") 121 Expect(err).ToNot(HaveOccurred()) 122 123 err = display.Stop() 124 Expect(err).ToNot(HaveOccurred()) 125 126 contents, err := ioutil.ReadFile(logFile1) 127 Expect(err).ToNot(HaveOccurred()) 128 Expect(string(contents)).To(Equal("Header: Value\n\n")) 129 130 contents, err = ioutil.ReadFile(logFile2) 131 Expect(err).ToNot(HaveOccurred()) 132 Expect(string(contents)).To(Equal("Header: Value\n\n")) 133 }) 134 }) 135 136 Describe("DisplayHost", func() { 137 It("writes the host", func() { 138 err := display.DisplayHost("banana") 139 Expect(err).ToNot(HaveOccurred()) 140 141 err = display.Stop() 142 Expect(err).ToNot(HaveOccurred()) 143 144 contents, err := ioutil.ReadFile(logFile1) 145 Expect(err).ToNot(HaveOccurred()) 146 Expect(string(contents)).To(Equal("Host: banana\n\n")) 147 148 contents, err = ioutil.ReadFile(logFile2) 149 Expect(err).ToNot(HaveOccurred()) 150 Expect(string(contents)).To(Equal("Host: banana\n\n")) 151 }) 152 }) 153 154 Describe("DisplayJSONBody", func() { 155 When("provided well formed JSON", func() { 156 It("writes a formatted output", func() { 157 raw := `{"a":"b", "c":"d", "don't escape HTML":"<&>"}` 158 formatted := `{ 159 "a": "b", 160 "c": "d", 161 "don't escape HTML": "<&>" 162 } 163 164 ` // Additional spaces required 165 err := display.DisplayJSONBody([]byte(raw)) 166 Expect(err).ToNot(HaveOccurred()) 167 168 err = display.Stop() 169 Expect(err).ToNot(HaveOccurred()) 170 171 contents, err := ioutil.ReadFile(logFile1) 172 Expect(err).ToNot(HaveOccurred()) 173 Expect(string(contents)).To(Equal(formatted)) 174 175 contents, err = ioutil.ReadFile(logFile2) 176 Expect(err).ToNot(HaveOccurred()) 177 Expect(string(contents)).To(Equal(formatted)) 178 }) 179 }) 180 181 When("the body is empty", func() { 182 It("does not write the body", func() { 183 err := display.DisplayJSONBody(nil) 184 Expect(err).ToNot(HaveOccurred()) 185 186 err = display.Stop() 187 Expect(err).ToNot(HaveOccurred()) 188 189 contents, err := ioutil.ReadFile(logFile1) 190 Expect(err).ToNot(HaveOccurred()) 191 Expect(string(contents)).To(Equal("\n")) 192 193 contents, err = ioutil.ReadFile(logFile2) 194 Expect(err).ToNot(HaveOccurred()) 195 Expect(string(contents)).To(Equal("\n")) 196 }) 197 }) 198 199 When("provided malformed JSON", func() { 200 It("displays the raw body", func() { 201 raw := `[{"data":1, "banana": 2}` 202 err := display.DisplayJSONBody([]byte(raw)) 203 Expect(err).ToNot(HaveOccurred()) 204 205 err = display.Stop() 206 Expect(err).ToNot(HaveOccurred()) 207 208 contents, err := ioutil.ReadFile(logFile1) 209 Expect(err).ToNot(HaveOccurred()) 210 Expect(string(contents)).To(Equal(raw + "\n\n")) 211 212 contents, err = ioutil.ReadFile(logFile2) 213 Expect(err).ToNot(HaveOccurred()) 214 Expect(string(contents)).To(Equal(raw + "\n\n")) 215 }) 216 }) 217 }) 218 219 Describe("DisplayMessage", func() { 220 It("writes the message", func() { 221 msg := "i am a message!!!!" 222 err := display.DisplayMessage(msg) 223 Expect(err).ToNot(HaveOccurred()) 224 225 err = display.Stop() 226 Expect(err).ToNot(HaveOccurred()) 227 228 contents, err := ioutil.ReadFile(logFile1) 229 Expect(err).ToNot(HaveOccurred()) 230 Expect(string(contents)).To(ContainSubstring(msg)) 231 232 contents, err = ioutil.ReadFile(logFile2) 233 Expect(err).ToNot(HaveOccurred()) 234 Expect(string(contents)).To(ContainSubstring(msg)) 235 }) 236 }) 237 238 Describe("DisplayRequestHeader", func() { 239 It("writes the method, uri and http protocol", func() { 240 err := display.DisplayRequestHeader("GET", "/v2/spaces/guid/summary", "HTTP/1.1") 241 Expect(err).ToNot(HaveOccurred()) 242 243 err = display.Stop() 244 Expect(err).ToNot(HaveOccurred()) 245 246 contents, err := ioutil.ReadFile(logFile1) 247 Expect(err).ToNot(HaveOccurred()) 248 Expect(string(contents)).To(Equal("GET /v2/spaces/guid/summary HTTP/1.1\n\n")) 249 250 contents, err = ioutil.ReadFile(logFile2) 251 Expect(err).ToNot(HaveOccurred()) 252 Expect(string(contents)).To(Equal("GET /v2/spaces/guid/summary HTTP/1.1\n\n")) 253 }) 254 }) 255 256 Describe("DisplayResponseHeader", func() { 257 It("writes the method, uri and http protocol", func() { 258 err := display.DisplayResponseHeader("HTTP/1.1", "200 OK") 259 Expect(err).ToNot(HaveOccurred()) 260 261 err = display.Stop() 262 Expect(err).ToNot(HaveOccurred()) 263 264 contents, err := ioutil.ReadFile(logFile1) 265 Expect(err).ToNot(HaveOccurred()) 266 Expect(string(contents)).To(Equal("HTTP/1.1 200 OK\n\n")) 267 268 contents, err = ioutil.ReadFile(logFile2) 269 Expect(err).ToNot(HaveOccurred()) 270 Expect(string(contents)).To(Equal("HTTP/1.1 200 OK\n\n")) 271 }) 272 }) 273 274 Describe("DisplayType", func() { 275 It("writes the passed type and time in localized ISO 8601", func() { 276 passedTime := time.Now() 277 err := display.DisplayType("banana", passedTime) 278 Expect(err).ToNot(HaveOccurred()) 279 280 err = display.Stop() 281 Expect(err).ToNot(HaveOccurred()) 282 283 contents, err := ioutil.ReadFile(logFile1) 284 Expect(err).ToNot(HaveOccurred()) 285 Expect(string(contents)).To(Equal(fmt.Sprintf("banana: [%s]\n\n", passedTime.Format(time.RFC3339)))) 286 287 contents, err = ioutil.ReadFile(logFile2) 288 Expect(err).ToNot(HaveOccurred()) 289 Expect(string(contents)).To(Equal(fmt.Sprintf("banana: [%s]\n\n", passedTime.Format(time.RFC3339)))) 290 }) 291 }) 292 293 Describe("HandleInternalError", func() { 294 It("sends error to standard error", func() { 295 err := errors.New("foobar") 296 display.HandleInternalError(err) 297 Expect(ui.Err).To(Say("foobar")) 298 Expect(display.Stop()).NotTo(HaveOccurred()) 299 }) 300 }) 301 302 Describe("Start and Stop", func() { 303 BeforeEach(func() { 304 // Cleanup old display output directory 305 Expect(display.Stop()).NotTo(HaveOccurred()) 306 logFile1 = filepath.Join(tmpdir, "tmp_sub_dir", "tmpfile3") 307 logFile2 = filepath.Join(tmpdir, "tmp", "sub", "dir", ".", "tmpfile4") 308 display = ui.RequestLoggerFileWriter([]string{logFile1, logFile2}) 309 }) 310 311 It("locks and then unlocks the mutex properly", func() { // and creates the intermediate dirs 312 c := make(chan bool) 313 go func() { 314 Expect(display.Start()).ToNot(HaveOccurred()) 315 c <- true 316 }() 317 Eventually(c).Should(Receive()) 318 Expect(display.Stop()).NotTo(HaveOccurred()) 319 }) 320 }) 321 }) 322 323 Describe("when the log file path is invalid", func() { 324 var pathName string 325 326 BeforeEach(func() { 327 var err error 328 tmpdir, err = ioutil.TempDir("", "request_logger") 329 Expect(err).ToNot(HaveOccurred()) 330 331 pathName = filepath.Join(tmpdir, "foo") 332 }) 333 334 AfterEach(func() { 335 Expect(display.Stop()).NotTo(HaveOccurred()) 336 Expect(os.RemoveAll(tmpdir)).NotTo(HaveOccurred()) 337 }) 338 339 It("returns the os error when we unsuccessfully try to write to a file", func() { 340 Expect(os.Mkdir(pathName, os.ModeDir|os.ModePerm)).NotTo(HaveOccurred()) 341 display = ui.RequestLoggerFileWriter([]string{pathName}) 342 err := display.Start() 343 344 Expect(err).To(MatchError(fmt.Sprintf("open %s: is a directory", pathName))) 345 }) 346 347 It("returns the os error when the parent directory for the log file is in the root directory", func() { 348 file, err := os.OpenFile(pathName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) 349 Expect(err).ToNot(HaveOccurred()) 350 _, err = file.WriteString("hello world") 351 Expect(err).ToNot(HaveOccurred()) 352 err = file.Close() 353 Expect(err).ToNot(HaveOccurred()) 354 355 display = ui.RequestLoggerFileWriter([]string{filepath.Join(pathName, "bar")}) 356 err = display.Start() 357 358 pathName = strings.Replace(pathName, `\`, `\\`, -1) 359 Expect(err).To(MatchError(MatchRegexp(fmt.Sprintf("mkdir %s", pathName)))) 360 }) 361 }) 362 363 Describe("UI", func() { 364 Describe("RequestLoggerFileWriter", func() { 365 It("returns a RequestLoggerFileWriter with the consistent filewriting mutex", func() { 366 logger1 := ui.RequestLoggerFileWriter(nil) 367 logger2 := ui.RequestLoggerFileWriter(nil) 368 369 c := make(chan bool) 370 err := logger1.Start() 371 Expect(err).ToNot(HaveOccurred()) 372 go func() { 373 Expect(logger2.Start()).ToNot(HaveOccurred()) 374 c <- true 375 }() 376 Consistently(c).ShouldNot(Receive()) 377 Expect(logger1.Stop()).ToNot(HaveOccurred()) 378 Eventually(c).Should(Receive()) 379 }) 380 }) 381 }) 382 })