Skip to content

Commit ce25dcb

Browse files
committed
test(e2e): cover gateway List API
Summary: Adds full-stack coverage for the gateway List RPC, including a dedicated e2e queue and assertions for filtering, pagination, and terminal summaries. Test Plan: ✅ `make fmt && make build && make test && make check-mocks && make e2e-test`
1 parent 61b04fe commit ce25dcb

4 files changed

Lines changed: 136 additions & 0 deletions

File tree

service/submitqueue/gateway/server/queues.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
queues:
77
- name: test-queue
88
- name: e2e-test-queue
9+
- name: e2e-list-queue
910
- name: e2e-cancel-queue
1011
# Routes to an analyzer that always errors (conflictfake.FailAlways) so e2e can
1112
# exercise the conflict-analysis error path. See newQueueRegistry in the

service/submitqueue/orchestrator/server/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,7 @@ func newQueueRegistry(logger *zap.Logger, scope tally.Scope, resolver changeset.
941941
byQueue: map[string]queueExtensions{
942942
"test-queue": testQueue,
943943
"e2e-test-queue": e2eQueue,
944+
"e2e-list-queue": e2eQueue,
944945
"e2e-conflict-error-queue": conflictErrQueue,
945946
"file-overlap-queue": fileOverlapQueue,
946947
},

test/e2e/submitqueue/harness_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,57 @@ func (s *E2EIntegrationSuite) land(queue string, uris ...string) string {
5151
return resp.Sqid
5252
}
5353

54+
// list calls the gateway List RPC and fails the test on unexpected errors.
55+
func (s *E2EIntegrationSuite) list(req *gatewaypb.ListRequest) *gatewaypb.ListResponse {
56+
t := s.T()
57+
resp, err := s.gatewayClient.List(s.ctx, req)
58+
require.NoError(t, err, "List failed for queue %s", req.Queue)
59+
return resp
60+
}
61+
62+
// awaitListContains polls List until all expected sqids are visible in one
63+
// response page. The gateway updates request summaries synchronously for Land
64+
// and asynchronously from the log topic for later statuses, so callers use the
65+
// same bounded polling style as Status.
66+
func (s *E2EIntegrationSuite) awaitListContains(req *gatewaypb.ListRequest, want ...string) *gatewaypb.ListResponse {
67+
t := s.T()
68+
var resp *gatewaypb.ListResponse
69+
require.Eventually(t, func() bool {
70+
var err error
71+
resp, err = s.gatewayClient.List(s.ctx, req)
72+
if err != nil {
73+
s.log.Logf("List(%s) not ready yet: %v", req.Queue, err)
74+
return false
75+
}
76+
got := summarySQIDs(resp.Requests)
77+
s.log.Logf("List(%s) = %v (want %v)", req.Queue, got, want)
78+
return containsAll(got, want)
79+
}, persistTimeout, persistPollInterval,
80+
"List(%s) should contain sqids %v", req.Queue, want)
81+
return resp
82+
}
83+
84+
func summarySQIDs(summaries []*gatewaypb.RequestSummary) []string {
85+
out := make([]string, len(summaries))
86+
for i, summary := range summaries {
87+
out[i] = summary.Sqid
88+
}
89+
return out
90+
}
91+
92+
func containsAll(got []string, want []string) bool {
93+
seen := make(map[string]struct{}, len(got))
94+
for _, sqid := range got {
95+
seen[sqid] = struct{}{}
96+
}
97+
for _, sqid := range want {
98+
if _, ok := seen[sqid]; !ok {
99+
return false
100+
}
101+
}
102+
return true
103+
}
104+
54105
// currentStatus reads the request's current customer-facing status via the
55106
// Status RPC. A transport error is returned so callers can keep polling.
56107
func (s *E2EIntegrationSuite) currentStatus(sqid string) (entity.RequestStatus, error) {

test/e2e/submitqueue/suite_test.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,89 @@ func (s *E2EIntegrationSuite) TestLand_HappyPath_ReachesLanded() {
218218
"operating store should show request %s in terminal state landed", sqid)
219219
}
220220

221+
// TestList_ReturnsFilteredPagedSummaries verifies the customer-facing List RPC
222+
// against the full stack. Land writes the initial summary through the gateway,
223+
// later status updates arrive through the request-log topic, and List reads the
224+
// gateway-owned summary read model through the public RPC surface.
225+
func (s *E2EIntegrationSuite) TestList_ReturnsFilteredPagedSummaries() {
226+
t := s.T()
227+
228+
startTimeMs := time.Now().Add(-time.Second).UnixMilli()
229+
firstURI := "github://uber/e2e-list/pull/101/abcdef0123456789abcdef0123456789abcdef01"
230+
secondURI := "github://uber/e2e-list/pull/102/abcdef0123456789abcdef0123456789abcdef02"
231+
otherURI := "github://uber/e2e-list-other/pull/201/abcdef0123456789abcdef0123456789abcdef03"
232+
233+
first := s.land("e2e-list-queue", firstURI)
234+
second := s.land("e2e-list-queue", secondURI)
235+
otherQueue := s.land("e2e-cancel-queue", otherURI)
236+
endTimeMs := time.Now().Add(time.Minute).UnixMilli()
237+
238+
resp := s.awaitListContains(&gatewaypb.ListRequest{
239+
Queue: "e2e-list-queue",
240+
StartTimeMs: startTimeMs,
241+
EndTimeMs: endTimeMs,
242+
PageSize: 10,
243+
}, first, second)
244+
245+
assert.NotContains(t, summarySQIDs(resp.Requests), otherQueue,
246+
"List should not return requests from a different queue")
247+
248+
bySQID := make(map[string]*gatewaypb.RequestSummary, len(resp.Requests))
249+
for _, summary := range resp.Requests {
250+
bySQID[summary.Sqid] = summary
251+
}
252+
253+
firstSummary := bySQID[first]
254+
require.NotNil(t, firstSummary, "List response should include %s", first)
255+
assert.Equal(t, "e2e-list-queue", firstSummary.Queue)
256+
assert.Equal(t, []string{firstURI}, firstSummary.ChangeUris)
257+
assert.NotEmpty(t, firstSummary.Status)
258+
assert.GreaterOrEqual(t, firstSummary.StartedAtMs, startTimeMs)
259+
assert.Less(t, firstSummary.StartedAtMs, endTimeMs)
260+
assert.GreaterOrEqual(t, firstSummary.UpdatedAtMs, firstSummary.StartedAtMs)
261+
262+
s.awaitStatus(first, entity.RequestStatusLanded)
263+
s.awaitStatus(second, entity.RequestStatusLanded)
264+
265+
landedResp := s.awaitListContains(&gatewaypb.ListRequest{
266+
Queue: "e2e-list-queue",
267+
StartTimeMs: startTimeMs,
268+
EndTimeMs: endTimeMs,
269+
Statuses: []string{string(entity.RequestStatusLanded)},
270+
PageSize: 10,
271+
}, first, second)
272+
for _, summary := range landedResp.Requests {
273+
if summary.Sqid != first && summary.Sqid != second {
274+
continue
275+
}
276+
assert.Equal(t, string(entity.RequestStatusLanded), summary.Status)
277+
assert.True(t, summary.Terminal, "landed summary %s should be terminal", summary.Sqid)
278+
assert.Greater(t, summary.CompletedAtMs, int64(0), "landed summary %s should have completion time", summary.Sqid)
279+
}
280+
281+
page1 := s.list(&gatewaypb.ListRequest{
282+
Queue: "e2e-list-queue",
283+
StartTimeMs: startTimeMs,
284+
EndTimeMs: endTimeMs,
285+
PageSize: 1,
286+
Sort: gatewaypb.ListSort_ADMITTED_DESC,
287+
})
288+
require.Len(t, page1.Requests, 1)
289+
require.NotEmpty(t, page1.NextPageToken)
290+
291+
page2 := s.list(&gatewaypb.ListRequest{
292+
Queue: "e2e-list-queue",
293+
StartTimeMs: startTimeMs,
294+
EndTimeMs: endTimeMs,
295+
PageSize: 1,
296+
PageToken: page1.NextPageToken,
297+
Sort: gatewaypb.ListSort_ADMITTED_DESC,
298+
})
299+
require.Len(t, page2.Requests, 1)
300+
assert.NotEqual(t, page1.Requests[0].Sqid, page2.Requests[0].Sqid)
301+
assert.ElementsMatch(t, []string{first, second}, []string{page1.Requests[0].Sqid, page2.Requests[0].Sqid})
302+
}
303+
221304
// TestCancelRequest_InvalidSqid verifies the gateway rejects an empty sqid
222305
// synchronously before publishing anything to the cancel queue.
223306
func (s *E2EIntegrationSuite) TestCancelRequest_InvalidSqid() {

0 commit comments

Comments
 (0)