github.com/jasonkeene/cli@v6.14.1-0.20160816203908-ca5715166dfb+incompatible/cf/api/logs/loggregator_logs_repository_test.go (about) 1 package logs_test 2 3 import ( 4 "github.com/cloudfoundry/cli/cf/api/authentication/authenticationfakes" 5 "github.com/cloudfoundry/cli/cf/api/logs/logsfakes" 6 "github.com/cloudfoundry/cli/cf/configuration/coreconfig" 7 "github.com/cloudfoundry/cli/cf/errors" 8 testconfig "github.com/cloudfoundry/cli/testhelpers/configuration" 9 "github.com/cloudfoundry/loggregatorlib/logmessage" 10 noaa_errors "github.com/cloudfoundry/noaa/errors" 11 "github.com/gogo/protobuf/proto" 12 13 "time" 14 15 . "github.com/cloudfoundry/cli/cf/api/logs" 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 ) 19 20 var _ = Describe("loggregator logs repository", func() { 21 var ( 22 fakeConsumer *logsfakes.FakeLoggregatorConsumer 23 logsRepo *LoggregatorLogsRepository 24 configRepo coreconfig.ReadWriter 25 authRepo *authenticationfakes.FakeRepository 26 ) 27 28 BeforeEach(func() { 29 fakeConsumer = new(logsfakes.FakeLoggregatorConsumer) 30 configRepo = testconfig.NewRepositoryWithDefaults() 31 configRepo.SetLoggregatorEndpoint("loggregator-server.test.com") 32 configRepo.SetAccessToken("the-access-token") 33 authRepo = &authenticationfakes.FakeRepository{} 34 }) 35 36 JustBeforeEach(func() { 37 logsRepo = NewLoggregatorLogsRepository(configRepo, fakeConsumer, authRepo) 38 }) 39 40 Describe("RecentLogsFor", func() { 41 Context("when a noaa_errors.UnauthorizedError occurs", func() { 42 var recentCalled bool 43 BeforeEach(func() { 44 fakeConsumer.RecentStub = func(string, string) ([]*logmessage.LogMessage, error) { 45 if recentCalled { 46 return nil, nil 47 } 48 recentCalled = true 49 return nil, noaa_errors.NewUnauthorizedError("i'm sorry dave") 50 } 51 }) 52 53 It("refreshes the access token", func() { 54 _, err := logsRepo.RecentLogsFor("app-guid") 55 Expect(err).ToNot(HaveOccurred()) 56 Expect(authRepo.RefreshAuthTokenCallCount()).To(Equal(1)) 57 }) 58 }) 59 60 Context("when an error occurs", func() { 61 BeforeEach(func() { 62 fakeConsumer.RecentReturns(nil, errors.New("oops")) 63 }) 64 65 It("returns the error", func() { 66 _, err := logsRepo.RecentLogsFor("app-guid") 67 Expect(err).To(Equal(errors.New("oops"))) 68 }) 69 }) 70 71 Context("when an error does not occur", func() { 72 var msg1, msg2 *logmessage.LogMessage 73 74 BeforeEach(func() { 75 msg1 = makeLogMessage("My message 2", int64(2000)) 76 msg2 = makeLogMessage("My message 1", int64(1000)) 77 78 fakeConsumer.RecentReturns([]*logmessage.LogMessage{ 79 msg1, 80 msg2, 81 }, nil) 82 }) 83 84 It("gets the logs for the requested app", func() { 85 logsRepo.RecentLogsFor("app-guid") 86 appGuid, _ := fakeConsumer.RecentArgsForCall(0) 87 Expect(appGuid).To(Equal("app-guid")) 88 }) 89 90 It("writes the sorted log messages onto the provided channel", func() { 91 messages, err := logsRepo.RecentLogsFor("app-guid") 92 Expect(err).NotTo(HaveOccurred()) 93 94 Expect(messages).To(Equal([]Loggable{ 95 NewLoggregatorLogMessage(msg2), 96 NewLoggregatorLogMessage(msg1), 97 })) 98 }) 99 }) 100 }) 101 102 Describe("tailing logs", func() { 103 var logChan chan Loggable 104 var errChan chan error 105 106 BeforeEach(func() { 107 logChan = make(chan Loggable) 108 errChan = make(chan error) 109 }) 110 111 Context("when an error occurs", func() { 112 e := errors.New("oops") 113 114 BeforeEach(func() { 115 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 116 return nil, e 117 } 118 }) 119 120 It("returns an error", func(done Done) { 121 go func() { 122 Eventually(errChan).Should(Receive(&e)) 123 124 close(done) 125 }() 126 127 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 128 }) 129 }) 130 131 Context("when a LoggregatorConsumer.UnauthorizedError occurs", func() { 132 It("refreshes the access token", func(done Done) { 133 calledOnce := false 134 135 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 136 if !calledOnce { 137 calledOnce = true 138 return nil, noaa_errors.NewUnauthorizedError("i'm sorry dave") 139 } else { 140 return nil, nil 141 } 142 } 143 144 go func() { 145 defer GinkgoRecover() 146 147 Eventually(authRepo.RefreshAuthTokenCallCount).Should(Equal(1)) 148 Consistently(errChan).ShouldNot(Receive()) 149 150 close(done) 151 }() 152 153 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 154 }) 155 156 Context("when LoggregatorConsumer.UnauthorizedError occurs again", func() { 157 It("returns an error", func(done Done) { 158 err := noaa_errors.NewUnauthorizedError("All the errors") 159 160 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 161 return nil, err 162 } 163 164 go func() { 165 defer GinkgoRecover() 166 167 // Not equivalent to ShouldNot(Receive(BeNil())) 168 // Should receive something, but it shouldn't be nil 169 Eventually(errChan).Should(Receive(&err)) 170 close(done) 171 }() 172 173 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 174 }) 175 }) 176 }) 177 178 Context("when no error occurs", func() { 179 It("asks for the logs for the given app", func() { 180 fakeConsumer.TailStub = func(appGuid, token string) (<-chan *logmessage.LogMessage, error) { 181 Expect(appGuid).To(Equal("app-guid")) 182 Expect(token).To(Equal("the-access-token")) 183 return nil, nil 184 } 185 186 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 187 }) 188 189 It("sets the on connect callback", func() { 190 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 191 return nil, nil 192 } 193 194 called := false 195 logsRepo.TailLogsFor("app-guid", func() { called = true }, logChan, errChan) 196 197 Expect(fakeConsumer.SetOnConnectCallbackCallCount()).To(Equal(1)) 198 // best way we could come up with to match on a callback function 199 callbackFunc := fakeConsumer.SetOnConnectCallbackArgsForCall(0) 200 callbackFunc() 201 Expect(called).To(Equal(true)) 202 }) 203 204 It("sorts the messages before yielding them", func() { 205 var receivedMessages []Loggable 206 msg3 := makeLogMessage("hello3", 300) 207 msg2 := makeLogMessage("hello2", 200) 208 msg1 := makeLogMessage("hello1", 100) 209 210 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 211 consumerLogChan := make(chan *logmessage.LogMessage) 212 go func() { 213 consumerLogChan <- msg3 214 consumerLogChan <- msg2 215 consumerLogChan <- msg1 216 217 close(consumerLogChan) 218 }() 219 220 return consumerLogChan, nil 221 } 222 223 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 224 225 for msg := range logChan { 226 receivedMessages = append(receivedMessages, msg) 227 228 if len(receivedMessages) == 3 { 229 break 230 } 231 } 232 233 Consistently(errChan).ShouldNot(Receive()) 234 235 Expect(receivedMessages).To(Equal([]Loggable{ 236 NewLoggregatorLogMessage(msg1), 237 NewLoggregatorLogMessage(msg2), 238 NewLoggregatorLogMessage(msg3), 239 })) 240 }) 241 242 It("flushes remaining log messages and closes the returned channel when Close is called", func() { 243 logsRepo.BufferTime = 10 * time.Second 244 245 msg3 := makeLogMessage("hello3", 300) 246 msg2 := makeLogMessage("hello2", 200) 247 msg1 := makeLogMessage("hello1", 100) 248 249 fakeConsumer.TailStub = func(_, _ string) (<-chan *logmessage.LogMessage, error) { 250 messageChan := make(chan *logmessage.LogMessage) 251 go func() { 252 messageChan <- msg3 253 messageChan <- msg2 254 messageChan <- msg1 255 close(messageChan) 256 }() 257 258 return messageChan, nil 259 } 260 261 Expect(fakeConsumer.CloseCallCount()).To(Equal(0)) 262 263 logsRepo.TailLogsFor("app-guid", func() {}, logChan, errChan) 264 Consistently(errChan).ShouldNot(Receive()) 265 266 logsRepo.Close() 267 268 Expect(fakeConsumer.CloseCallCount()).To(Equal(1)) 269 270 Eventually(logChan).Should(Receive(Equal(NewLoggregatorLogMessage(msg1)))) 271 Eventually(logChan).Should(Receive(Equal(NewLoggregatorLogMessage(msg2)))) 272 Eventually(logChan).Should(Receive(Equal(NewLoggregatorLogMessage(msg3)))) 273 }) 274 }) 275 }) 276 }) 277 278 func makeLogMessage(message string, timestamp int64) *logmessage.LogMessage { 279 messageType := logmessage.LogMessage_OUT 280 sourceName := "DEA" 281 return &logmessage.LogMessage{ 282 Message: []byte(message), 283 AppId: proto.String("my-app-guid"), 284 MessageType: &messageType, 285 SourceName: &sourceName, 286 Timestamp: proto.Int64(timestamp), 287 } 288 }