@@ -17,6 +17,7 @@ package controller
1717import (
1818 "context"
1919 "fmt"
20+ "strings"
2021 "testing"
2122
2223 "github.com/stretchr/testify/assert"
@@ -64,14 +65,9 @@ func newTestRegistryWithNoopPublisher(t *testing.T, ctrl *gomock.Controller) con
6465 return registry
6566}
6667
67- // noopStorage returns a storage.Storage whose RequestLogStore.Insert
68- // succeeds silently for any entityqueue.
68+ // noopStorage returns stateful request storage whose writes succeed.
6969func noopStorage (ctrl * gomock.Controller ) storage.Storage {
70- logStore := storagemock .NewMockRequestLogStore (ctrl )
71- logStore .EXPECT ().Insert (gomock .Any (), gomock .Any ()).Return (nil ).AnyTimes ()
72- store := storagemock .NewMockStorage (ctrl )
73- store .EXPECT ().GetRequestLogStore ().Return (logStore ).AnyTimes ()
74- return store
70+ return newControllerStorageFixture (ctrl ).storage
7571}
7672
7773// noopQueueConfigStore returns a mock queueconfig.Store that always reports
@@ -162,6 +158,48 @@ func TestLand_ReturnsErrorOnEmptyQueue(t *testing.T) {
162158 assert .True (t , IsInvalidRequest (err ))
163159}
164160
161+ func TestLand_ValidatesQueueLengthBeforeAllocatingSqid (t * testing.T ) {
162+ tests := []struct {
163+ name string
164+ queue string
165+ wantError bool
166+ }{
167+ {name : "maximum length" , queue : strings .Repeat ("q" , maxQueueIdentifierBytes )},
168+ {name : "over maximum" , queue : strings .Repeat ("q" , maxQueueIdentifierBytes + 1 ), wantError : true },
169+ }
170+
171+ for _ , tt := range tests {
172+ t .Run (tt .name , func (t * testing.T ) {
173+ ctrl := gomock .NewController (t )
174+ cnt := countermock .NewMockCounter (ctrl )
175+ if ! tt .wantError {
176+ cnt .EXPECT ().Next (gomock .Any (), gomock .Any ()).Return (int64 (1 ), nil )
177+ }
178+ controller := NewLandController (
179+ zap .NewNop ().Sugar (),
180+ tally .NoopScope ,
181+ cnt ,
182+ noopStorage (ctrl ),
183+ noopQueueConfigStore (ctrl ),
184+ newTestRegistryWithNoopPublisher (t , ctrl ),
185+ )
186+
187+ result , err := controller .Land (context .Background (), entity.LandRequest {
188+ Queue : tt .queue ,
189+ Change : change.Change {URIs : []string {"uri" }},
190+ })
191+
192+ if tt .wantError {
193+ require .Error (t , err )
194+ assert .True (t , IsInvalidRequest (err ))
195+ return
196+ }
197+ require .NoError (t , err )
198+ assert .Equal (t , tt .queue + "/1" , result .ID )
199+ })
200+ }
201+ }
202+
165203func TestLand_ReturnsErrorOnEmptyChangeUri (t * testing.T ) {
166204 ctrl := gomock .NewController (t )
167205
@@ -179,6 +217,39 @@ func TestLand_ReturnsErrorOnEmptyChangeUri(t *testing.T) {
179217 assert .True (t , IsInvalidRequest (err ))
180218}
181219
220+ func TestLand_ReturnsErrorOnInvalidChangeURIs (t * testing.T ) {
221+ tests := []struct {
222+ name string
223+ uris []string
224+ }{
225+ {name : "empty URI element" , uris : []string {"" }},
226+ {name : "URI exceeds storage limit" , uris : []string {strings .Repeat ("x" , maxStorageIdentifierBytes + 1 )}},
227+ {name : "duplicate exact URI" , uris : []string {"uri" , "uri" }},
228+ }
229+
230+ for _ , tt := range tests {
231+ t .Run (tt .name , func (t * testing.T ) {
232+ ctrl := gomock .NewController (t )
233+ controller := NewLandController (
234+ zap .NewNop ().Sugar (),
235+ tally .NoopScope ,
236+ countermock .NewMockCounter (ctrl ),
237+ noopStorage (ctrl ),
238+ noopQueueConfigStore (ctrl ),
239+ newTestRegistryWithNoopPublisher (t , ctrl ),
240+ )
241+
242+ _ , err := controller .Land (context .Background (), entity.LandRequest {
243+ Queue : "test-queue" ,
244+ Change : change.Change {URIs : tt .uris },
245+ })
246+
247+ require .Error (t , err )
248+ assert .True (t , IsInvalidRequest (err ))
249+ })
250+ }
251+ }
252+
182253func TestLand_ReturnsErrorOnZeroValueChange (t * testing.T ) {
183254 ctrl := gomock .NewController (t )
184255
@@ -238,22 +309,44 @@ func TestLand_PropagatesQueueConfigStoreError(t *testing.T) {
238309func TestLand_PublishesToQueue (t * testing.T ) {
239310 var publishedTopic string
240311 var publishedMessage entityqueue.Message
312+ var persistedSummary entity.RequestSummary
313+ var persistedLog entity.RequestLog
241314
242315 ctrl := gomock .NewController (t )
243316
244317 cnt := countermock .NewMockCounter (ctrl )
245318 cnt .EXPECT ().Next (gomock .Any (), gomock .Any ()).Return (int64 (123 ), nil )
246319
320+ store := storagemock .NewMockStorage (ctrl )
321+ summaryStore := storagemock .NewMockRequestSummaryStore (ctrl )
322+ logStore := storagemock .NewMockRequestLogStore (ctrl )
323+ store .EXPECT ().GetRequestSummaryStore ().Return (summaryStore ).AnyTimes ()
324+ store .EXPECT ().GetRequestLogStore ().Return (logStore ).AnyTimes ()
325+
247326 registry , publisher := newTestRegistry (t , ctrl )
248- publisher .EXPECT ().Publish (gomock .Any (), gomock .Any (), gomock .Any ()).DoAndReturn (
249- func (ctx context.Context , topic string , msg entityqueue.Message ) error {
250- publishedTopic = topic
251- publishedMessage = msg
252- return nil
253- },
327+ gomock .InOrder (
328+ summaryStore .EXPECT ().Create (gomock .Any (), gomock .Any ()).DoAndReturn (
329+ func (_ context.Context , summary entity.RequestSummary ) error {
330+ persistedSummary = summary
331+ return nil
332+ },
333+ ),
334+ publisher .EXPECT ().Publish (gomock .Any (), gomock .Any (), gomock .Any ()).DoAndReturn (
335+ func (_ context.Context , topic string , msg entityqueue.Message ) error {
336+ publishedTopic = topic
337+ publishedMessage = msg
338+ return nil
339+ },
340+ ),
341+ logStore .EXPECT ().Insert (gomock .Any (), gomock .Any ()).DoAndReturn (
342+ func (_ context.Context , log entity.RequestLog ) error {
343+ persistedLog = log
344+ return nil
345+ },
346+ ),
254347 )
255348
256- controller := NewLandController (zap .NewNop ().Sugar (), tally .NoopScope , cnt , noopStorage ( ctrl ) , noopQueueConfigStore (ctrl ), registry )
349+ controller := NewLandController (zap .NewNop ().Sugar (), tally .NoopScope , cnt , store , noopQueueConfigStore (ctrl ), registry )
257350 ctx := context .Background ()
258351
259352 req := entity.LandRequest {
@@ -266,6 +359,24 @@ func TestLand_PublishesToQueue(t *testing.T) {
266359 require .NoError (t , err )
267360 assert .Equal (t , "test-queue/123" , result .ID )
268361
362+ assert .Equal (t , entity.RequestSummary {
363+ RequestID : "test-queue/123" ,
364+ Queue : "test-queue" ,
365+ ChangeURIs : []string {"github://github.example.com/uber/backend/pull/456/fedcba9876543210fedcba9876543210fedcba98" },
366+ ReceivedAtMs : persistedSummary .ReceivedAtMs ,
367+ Status : entity .RequestStatusAccepting ,
368+ StatusTimestampMs : persistedSummary .ReceivedAtMs ,
369+ Version : 1 ,
370+ Metadata : map [string ]string {},
371+ }, persistedSummary )
372+ assert .Positive (t , persistedSummary .ReceivedAtMs )
373+ assert .Equal (t , entity.RequestLog {
374+ RequestID : "test-queue/123" ,
375+ TimestampMs : persistedSummary .ReceivedAtMs ,
376+ Status : entity .RequestStatusAccepted ,
377+ Metadata : map [string ]string {},
378+ }, persistedLog )
379+
269380 // Verify message was published to the topic registered under TopicKeyStart
270381 assert .Equal (t , "start" , publishedTopic )
271382 assert .Equal (t , "test-queue/123" , publishedMessage .ID )
@@ -280,20 +391,48 @@ func TestLand_PublishesToQueue(t *testing.T) {
280391 assert .Equal (t , mergestrategy .MergeStrategyRebase , deserializedReq .LandStrategy )
281392}
282393
283- func TestLand_ContinuesWhenPublishFails (t * testing.T ) {
394+ func TestLand_ReturnsErrorWhenPublishFails (t * testing.T ) {
284395 ctrl := gomock .NewController (t )
285396
286397 cnt := countermock .NewMockCounter (ctrl )
287398 cnt .EXPECT ().Next (gomock .Any (), gomock .Any ()).Return (int64 (999 ), nil )
288399
400+ store := storagemock .NewMockStorage (ctrl )
401+ summaryStore := storagemock .NewMockRequestSummaryStore (ctrl )
402+ store .EXPECT ().GetRequestSummaryStore ().Return (summaryStore )
403+ summaryStore .EXPECT ().Create (gomock .Any (), gomock .Any ()).DoAndReturn (func (_ context.Context , summary entity.RequestSummary ) error {
404+ assert .Equal (t , entity .RequestStatusAccepting , summary .Status )
405+ return nil
406+ })
407+
289408 registry , publisher := newTestRegistry (t , ctrl )
290409 publisher .EXPECT ().Publish (gomock .Any (), gomock .Any (), gomock .Any ()).Return (fmt .Errorf ("queue unavailable" ))
291410
292- controller := NewLandController (zap .NewNop ().Sugar (), tally .NoopScope , cnt , noopStorage ( ctrl ) , noopQueueConfigStore (ctrl ), registry )
411+ controller := NewLandController (zap .NewNop ().Sugar (), tally .NoopScope , cnt , store , noopQueueConfigStore (ctrl ), registry )
293412 ctx := context .Background ()
294413
295414 _ , err := controller .Land (ctx , testLandRequest ("test-queue" ))
296415
297- // Should fail if publish fails
298416 require .Error (t , err )
299417}
418+
419+ func TestLand_ReturnsSqidWhenAcceptedLogFailsAfterPublish (t * testing.T ) {
420+ ctrl := gomock .NewController (t )
421+ cnt := countermock .NewMockCounter (ctrl )
422+ cnt .EXPECT ().Next (gomock .Any (), gomock .Any ()).Return (int64 (999 ), nil )
423+ fixture := newControllerStorageFixture (ctrl )
424+ fixture .setLogInsertError (fmt .Errorf ("log unavailable" ))
425+
426+ controller := NewLandController (
427+ zap .NewNop ().Sugar (),
428+ tally .NoopScope ,
429+ cnt ,
430+ fixture .storage ,
431+ noopQueueConfigStore (ctrl ),
432+ newTestRegistryWithNoopPublisher (t , ctrl ),
433+ )
434+ result , err := controller .Land (context .Background (), testLandRequest ("test-queue" ))
435+
436+ require .NoError (t , err )
437+ assert .Equal (t , "test-queue/999" , result .ID )
438+ }
0 commit comments