github.com/elopio/cli@v6.21.2-0.20160902224010-ea909d1fdb2f+incompatible/cf/api/logs/noaa_logs_repository_test.go (about) 1 package logs_test 2 3 import ( 4 "errors" 5 "reflect" 6 "time" 7 8 "code.cloudfoundry.org/cli/cf/configuration/coreconfig" 9 noaa_errors "github.com/cloudfoundry/noaa/errors" 10 "github.com/cloudfoundry/sonde-go/events" 11 "github.com/gogo/protobuf/proto" 12 13 "code.cloudfoundry.org/cli/cf/api/authentication/authenticationfakes" 14 testapi "code.cloudfoundry.org/cli/cf/api/logs/logsfakes" 15 testconfig "code.cloudfoundry.org/cli/testhelpers/configuration" 16 17 "sync" 18 19 "code.cloudfoundry.org/cli/cf/api/logs" 20 . "github.com/onsi/ginkgo" 21 . "github.com/onsi/gomega" 22 ) 23 24 var _ = Describe("logs with noaa repository", func() { 25 var ( 26 fakeNoaaConsumer *testapi.FakeNoaaConsumer 27 config coreconfig.ReadWriter 28 fakeTokenRefresher *authenticationfakes.FakeRepository 29 repo *logs.NoaaLogsRepository 30 ) 31 32 BeforeEach(func() { 33 fakeNoaaConsumer = &testapi.FakeNoaaConsumer{} 34 config = testconfig.NewRepositoryWithDefaults() 35 config.SetLoggregatorEndpoint("loggregator.test.com") 36 config.SetDopplerEndpoint("doppler.test.com") 37 config.SetAccessToken("the-access-token") 38 fakeTokenRefresher = &authenticationfakes.FakeRepository{} 39 repo = logs.NewNoaaLogsRepository(config, fakeNoaaConsumer, fakeTokenRefresher) 40 }) 41 42 Describe("RecentLogsFor", func() { 43 It("refreshes token and get metric once more if token has expired.", func() { 44 var recentLogsCallCount int 45 46 fakeNoaaConsumer.RecentLogsStub = func(appGuid, authToken string) ([]*events.LogMessage, error) { 47 defer func() { 48 recentLogsCallCount += 1 49 }() 50 51 if recentLogsCallCount == 0 { 52 return []*events.LogMessage{}, noaa_errors.NewUnauthorizedError("Unauthorized token") 53 } 54 55 return []*events.LogMessage{}, nil 56 } 57 58 repo.RecentLogsFor("app-guid") 59 Expect(fakeTokenRefresher.RefreshAuthTokenCallCount()).To(Equal(1)) 60 Expect(fakeNoaaConsumer.RecentLogsCallCount()).To(Equal(2)) 61 }) 62 63 It("refreshes token and get metric once more if token has expired.", func() { 64 fakeNoaaConsumer.RecentLogsReturns([]*events.LogMessage{}, errors.New("error error error")) 65 66 _, err := repo.RecentLogsFor("app-guid") 67 Expect(err).To(HaveOccurred()) 68 Expect(err.Error()).To(Equal("error error error")) 69 }) 70 71 Context("when an error does not occur", func() { 72 var msg1, msg2, msg3 *events.LogMessage 73 74 BeforeEach(func() { 75 msg1 = makeNoaaLogMessage("message 1", 1000) 76 msg2 = makeNoaaLogMessage("message 2", 2000) 77 msg3 = makeNoaaLogMessage("message 3", 3000) 78 79 fakeNoaaConsumer.RecentLogsReturns([]*events.LogMessage{ 80 msg3, 81 msg2, 82 msg1, 83 }, nil) 84 }) 85 86 It("gets the logs for the requested app", func() { 87 repo.RecentLogsFor("app-guid-1") 88 arg, _ := fakeNoaaConsumer.RecentLogsArgsForCall(0) 89 Expect(arg).To(Equal("app-guid-1")) 90 }) 91 92 It("returns the sorted log messages", func() { 93 messages, err := repo.RecentLogsFor("app-guid") 94 Expect(err).NotTo(HaveOccurred()) 95 96 Expect(messages).To(Equal([]logs.Loggable{ 97 logs.NewNoaaLogMessage(msg1), 98 logs.NewNoaaLogMessage(msg2), 99 logs.NewNoaaLogMessage(msg3), 100 })) 101 }) 102 }) 103 }) 104 105 Describe("TailLogsFor", func() { 106 var errChan chan error 107 var logChan chan logs.Loggable 108 109 AfterEach(func() { 110 Eventually(errChan).Should(BeClosed()) 111 Eventually(logChan).Should(BeClosed()) 112 }) 113 114 Context("when an error occurs", func() { 115 var e chan error 116 var c chan *events.LogMessage 117 118 BeforeEach(func() { 119 errChan = make(chan error) 120 logChan = make(chan logs.Loggable) 121 122 e = make(chan error) 123 c = make(chan *events.LogMessage) 124 125 fakeNoaaConsumer.CloseStub = func() error { 126 close(e) 127 close(c) 128 return nil 129 } 130 }) 131 132 It("returns an error when it occurs", func(done Done) { 133 defer repo.Close() 134 err := errors.New("oops") 135 136 fakeNoaaConsumer.TailingLogsWithoutReconnectStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 137 go func() { 138 e <- err 139 }() 140 return c, e 141 } 142 go repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 143 144 Eventually(errChan).Should(Receive(&err)) 145 146 close(done) 147 }) 148 }) 149 150 Context("when a noaa_errors.UnauthorizedError occurs", func() { 151 var e chan error 152 var c chan *events.LogMessage 153 154 BeforeEach(func() { 155 errChan = make(chan error) 156 logChan = make(chan logs.Loggable) 157 158 e = make(chan error) 159 c = make(chan *events.LogMessage) 160 161 fakeNoaaConsumer.CloseStub = func() error { 162 close(e) 163 close(c) 164 return nil 165 } 166 }) 167 168 It("refreshes the access token and tail logs once more", func(done Done) { 169 defer repo.Close() 170 calledOnce := false 171 err := errors.New("2nd Error") 172 synchronization := make(chan bool) 173 174 fakeNoaaConsumer.CloseStub = func() error { 175 synchronization <- true 176 return nil 177 } 178 179 fakeNoaaConsumer.TailingLogsWithoutReconnectStub = func(appGuid string, authToken string) (<-chan *events.LogMessage, <-chan error) { 180 ec := make(chan error) 181 lc := make(chan *events.LogMessage) 182 183 go func() { 184 if !calledOnce { 185 calledOnce = true 186 ec <- noaa_errors.NewUnauthorizedError("i'm sorry dave") 187 } else { 188 ec <- err 189 <-synchronization 190 close(ec) 191 close(lc) 192 } 193 }() 194 195 return lc, ec 196 } 197 198 go repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 199 200 Eventually(errChan).Should(Receive(&err)) 201 Eventually(fakeTokenRefresher.RefreshAuthTokenCallCount).Should(Equal(1)) 202 203 close(done) 204 }) 205 }) 206 207 Context("when no error occurs", func() { 208 var e chan error 209 var c chan *events.LogMessage 210 211 BeforeEach(func() { 212 errChan = make(chan error) 213 logChan = make(chan logs.Loggable) 214 215 e = make(chan error) 216 c = make(chan *events.LogMessage) 217 218 fakeNoaaConsumer.CloseStub = func() error { 219 close(e) 220 close(c) 221 return nil 222 } 223 }) 224 225 It("asks for the logs for the given app", func(done Done) { 226 defer repo.Close() 227 228 fakeNoaaConsumer.TailingLogsWithoutReconnectReturns(c, e) 229 230 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 231 232 Eventually(fakeNoaaConsumer.TailingLogsWithoutReconnectCallCount).Should(Equal(1)) 233 appGuid, token := fakeNoaaConsumer.TailingLogsWithoutReconnectArgsForCall(0) 234 Expect(appGuid).To(Equal("app-guid")) 235 Expect(token).To(Equal("the-access-token")) 236 237 close(done) 238 }, 2) 239 240 It("sets the on connect callback", func() { 241 defer repo.Close() 242 243 fakeNoaaConsumer.TailingLogsWithoutReconnectReturns(c, e) 244 245 var cb = func() { return } 246 repo.TailLogsFor("app-guid", cb, logChan, errChan) 247 248 Expect(fakeNoaaConsumer.SetOnConnectCallbackCallCount()).To(Equal(1)) 249 arg := fakeNoaaConsumer.SetOnConnectCallbackArgsForCall(0) 250 Expect(reflect.ValueOf(arg).Pointer() == reflect.ValueOf(cb).Pointer()).To(BeTrue()) 251 }) 252 }) 253 254 Context("and the buffer time is sufficient for sorting", func() { 255 var msg1, msg2, msg3 *events.LogMessage 256 var ec chan error 257 var lc chan *events.LogMessage 258 var syncMu sync.Mutex 259 260 BeforeEach(func() { 261 msg1 = makeNoaaLogMessage("hello1", 100) 262 msg2 = makeNoaaLogMessage("hello2", 200) 263 msg3 = makeNoaaLogMessage("hello3", 300) 264 265 errChan = make(chan error) 266 logChan = make(chan logs.Loggable) 267 ec = make(chan error) 268 269 syncMu.Lock() 270 lc = make(chan *events.LogMessage) 271 syncMu.Unlock() 272 273 fakeNoaaConsumer.TailingLogsWithoutReconnectStub = func(string, string) (<-chan *events.LogMessage, <-chan error) { 274 go func() { 275 syncMu.Lock() 276 lc <- msg3 277 lc <- msg2 278 lc <- msg1 279 syncMu.Unlock() 280 }() 281 282 return lc, ec 283 } 284 }) 285 286 JustBeforeEach(func() { 287 repo = logs.NewNoaaLogsRepository(config, fakeNoaaConsumer, fakeTokenRefresher) 288 289 fakeNoaaConsumer.CloseStub = func() error { 290 syncMu.Lock() 291 close(lc) 292 syncMu.Unlock() 293 close(ec) 294 295 return nil 296 } 297 }) 298 299 Context("when the channels are closed before reading", func() { 300 It("sorts the messages before yielding them", func(done Done) { 301 receivedMessages := []logs.Loggable{} 302 303 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 304 Consistently(errChan).ShouldNot(Receive()) 305 306 m := <-logChan 307 receivedMessages = append(receivedMessages, m) 308 m = <-logChan 309 receivedMessages = append(receivedMessages, m) 310 m = <-logChan 311 receivedMessages = append(receivedMessages, m) 312 repo.Close() 313 314 Expect(receivedMessages).To(Equal([]logs.Loggable{ 315 logs.NewNoaaLogMessage(msg1), 316 logs.NewNoaaLogMessage(msg2), 317 logs.NewNoaaLogMessage(msg3), 318 })) 319 close(done) 320 }) 321 }) 322 323 Context("when the channels are read while being written to", func() { 324 It("sorts the messages before yielding them", func(done Done) { 325 receivedMessages := []logs.Loggable{} 326 327 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 328 Consistently(errChan).ShouldNot(Receive()) 329 330 m := <-logChan 331 receivedMessages = append(receivedMessages, m) 332 m = <-logChan 333 receivedMessages = append(receivedMessages, m) 334 m = <-logChan 335 receivedMessages = append(receivedMessages, m) 336 337 repo.Close() 338 339 Expect(receivedMessages).To(Equal([]logs.Loggable{ 340 logs.NewNoaaLogMessage(msg1), 341 logs.NewNoaaLogMessage(msg2), 342 logs.NewNoaaLogMessage(msg3), 343 })) 344 345 close(done) 346 }) 347 348 It("flushes remaining log messages when Close is called", func() { 349 repo.BufferTime = 10 * time.Second 350 351 repo.TailLogsFor("app-guid", func() {}, logChan, errChan) 352 Consistently(errChan).ShouldNot(Receive()) 353 Consistently(logChan).ShouldNot(Receive()) 354 355 repo.Close() 356 357 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg1)))) 358 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg2)))) 359 Eventually(logChan).Should(Receive(Equal(logs.NewNoaaLogMessage(msg3)))) 360 }) 361 }) 362 }) 363 }) 364 }) 365 366 func makeNoaaLogMessage(message string, timestamp int64) *events.LogMessage { 367 messageType := events.LogMessage_OUT 368 sourceName := "DEA" 369 return &events.LogMessage{ 370 Message: []byte(message), 371 AppId: proto.String("app-guid"), 372 MessageType: &messageType, 373 SourceType: &sourceName, 374 Timestamp: proto.Int64(timestamp), 375 } 376 }