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  }