From d74de0fdaca95b988e64ca1ceddb5959e7487fa3 Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Wed, 2 Sep 2026 23:29:06 +0000 Subject: [PATCH 1/3] feat(stovepipe): read request history by URI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: Intent: - Support history lookup from an exact commit URI while preserving log-only authority. - Keep the plural response model ready for a future multi-attempt URI index. Changes: - Resolve the current URI mapping and load its retained request log. - Return one grouped history under the insert-once mapping contract. - Cover selector validation, missing history, infrastructure errors, and metric tags. This PR builds on #670, which adds request-ID history reads. --- Generated by the 🪄 [pr-create](https://sg.uberinternal.com/code.uber.internal/uber-code/devexp-agent-marketplace/-/blob/claude-code/plugins/dev/uber-dev/skills/pr-create/SKILL.md) skill in devexp-agent-marketplace --- stovepipe/controller/read_errors.go | 4 + stovepipe/controller/request_history.go | 53 +++++++- stovepipe/controller/request_history_test.go | 133 +++++++++++++++++-- 3 files changed, 178 insertions(+), 12 deletions(-) diff --git a/stovepipe/controller/read_errors.go b/stovepipe/controller/read_errors.go index 1bbeff6b..d81153c1 100644 --- a/stovepipe/controller/read_errors.go +++ b/stovepipe/controller/read_errors.go @@ -44,10 +44,14 @@ func validateHistoryIdentifier(name, value string) error { // RequestHistoryNotFoundError indicates that no retained history exists for a selector. type RequestHistoryNotFoundError struct { RequestID string + URI string } // Error implements error. func (e *RequestHistoryNotFoundError) Error() string { + if e.RequestID == "" { + return fmt.Sprintf("request history not found for URI %q", e.URI) + } return fmt.Sprintf("request history not found for request ID %q", e.RequestID) } diff --git a/stovepipe/controller/request_history.go b/stovepipe/controller/request_history.go index 37e2ea6d..d4adadb6 100644 --- a/stovepipe/controller/request_history.go +++ b/stovepipe/controller/request_history.go @@ -29,6 +29,7 @@ import ( // RequestHistoryController handles retained request-history lookups. type RequestHistoryController interface { GetRequestHistoryByID(ctx context.Context, req entity.GetRequestHistoryByIDRequest) ([]entity.RequestLog, error) + GetRequestHistoryByURI(ctx context.Context, req entity.GetRequestHistoryByURIRequest) ([]entity.RequestHistory, error) } var _ RequestHistoryController = (*requestHistoryController)(nil) @@ -78,13 +79,57 @@ func (c *requestHistoryController) readHistoryByID(ctx context.Context, req enti return nil, fmt.Errorf("GetRequestHistoryByID failed to resolve storage for queue %q: %w", req.Queue, err) } - logs, err := stores.GetRequestLogStore().List(ctx, req.ID) + logs, err := loadRequestLogs(ctx, stores.GetRequestLogStore(), req.ID, &RequestHistoryNotFoundError{RequestID: req.ID}) if err != nil { - if storage.IsNotFound(err) { - return nil, errs.NewUserError(&RequestHistoryNotFoundError{RequestID: req.ID}) - } return nil, fmt.Errorf("GetRequestHistoryByID failed to list request logs request_id=%s: %w", req.ID, err) } return logs, nil } + +// GetRequestHistoryByURI returns the retained history mapped to an exact commit URI. +func (c *requestHistoryController) GetRequestHistoryByURI(ctx context.Context, req entity.GetRequestHistoryByURIRequest) (histories []entity.RequestHistory, retErr error) { + op := metrics.Begin(c.metricsScope, "get_by_uri", metrics.StorageLatencyBuckets, metrics.TagsFromContext(ctx)...) + defer func() { op.Complete(retErr) }() + + if err := validateHistoryIdentifier("queue", req.Queue); err != nil { + return nil, fmt.Errorf("GetRequestHistoryByURI invalid queue: %w", err) + } + if err := validateHistoryIdentifier("URI", req.URI); err != nil { + return nil, fmt.Errorf("GetRequestHistoryByURI invalid request: %w", err) + } + + stores, err := c.stores.For(storage.Config{QueueName: req.Queue}) + if err != nil { + return nil, fmt.Errorf("GetRequestHistoryByURI failed to resolve storage for queue %q: %w", req.Queue, err) + } + + requestID, err := stores.GetRequestURIStore().GetIDByURI(ctx, req.URI) + if err != nil { + if storage.IsNotFound(err) { + return nil, errs.NewUserError(&RequestHistoryNotFoundError{URI: req.URI}) + } + return nil, fmt.Errorf("GetRequestHistoryByURI failed to resolve request URI %s: %w", req.URI, err) + } + + logs, err := loadRequestLogs(ctx, stores.GetRequestLogStore(), requestID, &RequestHistoryNotFoundError{URI: req.URI}) + if err != nil { + return nil, fmt.Errorf("GetRequestHistoryByURI failed to list request logs uri=%s request_id=%s: %w", req.URI, requestID, err) + } + + c.logger.Debugw("request history retrieved by URI", + "uri", req.URI, + "request_id", requestID, + "queue", req.Queue, + "event_count", len(logs), + ) + return []entity.RequestHistory{{RequestID: requestID, Events: logs}}, nil +} + +func loadRequestLogs(ctx context.Context, store storage.RequestLogStore, requestID string, notFound *RequestHistoryNotFoundError) ([]entity.RequestLog, error) { + logs, err := store.List(ctx, requestID) + if storage.IsNotFound(err) { + return nil, errs.NewUserError(notFound) + } + return logs, err +} diff --git a/stovepipe/controller/request_history_test.go b/stovepipe/controller/request_history_test.go index bd57e66b..dc37d4d0 100644 --- a/stovepipe/controller/request_history_test.go +++ b/stovepipe/controller/request_history_test.go @@ -125,29 +125,146 @@ func TestGetRequestHistoryByID(t *testing.T) { start, ok := snapshot.Counters()["test.request_history_controller.get_by_id.start+queue=context-queue"] require.True(t, ok) assert.EqualValues(t, 1, start.Value()) - assertOperationFinishIncludesContextTag(t, snapshot, err == nil) + assertOperationFinishIncludesContextTag(t, snapshot, "get_by_id", err == nil) + }) + } +} + +func TestGetRequestHistoryByURI(t *testing.T) { + const ( + queue = "monorepo/main" + uri = "git://example.com/repo.git/commit/deadbeef" + requestID = "request/monorepo/main/42" + ) + backendErr := errors.New("backend unavailable") + logs := []entity.RequestLog{ + {ID: "state/1", RequestID: requestID, TimestampMs: 10, State: entity.RequestStateAccepted}, + {ID: "event/a", RequestID: requestID, TimestampMs: 20, Event: entity.RequestEventBuildTriggered}, + {ID: "event/a", RequestID: requestID, TimestampMs: 20, Event: entity.RequestEventBuildTriggered}, + } + wantHistory := []entity.RequestHistory{{RequestID: requestID, Events: logs}} + + tests := []struct { + name string + req entity.GetRequestHistoryByURIRequest + mappedID string + factoryErr error + mappingErr error + listErr error + want []entity.RequestHistory + wantInvalid bool + wantNotFound bool + wantCause error + wantLog bool + }{ + {name: "singleton history preserves log order and duplicates", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, want: wantHistory, wantLog: true}, + {name: "empty queue", req: entity.GetRequestHistoryByURIRequest{URI: uri}, wantInvalid: true}, + {name: "oversized queue", req: entity.GetRequestHistoryByURIRequest{Queue: strings.Repeat("q", maxHistoryIdentifierBytes+1), URI: uri}, wantInvalid: true}, + {name: "empty URI", req: entity.GetRequestHistoryByURIRequest{Queue: queue}, wantInvalid: true}, + {name: "oversized URI", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: strings.Repeat("u", maxHistoryIdentifierBytes+1)}, wantInvalid: true}, + {name: "storage factory failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, factoryErr: backendErr, wantCause: backendErr}, + {name: "URI mapping not found", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappingErr: fmt.Errorf("lookup: %w", storage.ErrNotFound), wantNotFound: true}, + {name: "URI store failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappingErr: backendErr, wantCause: backendErr}, + {name: "mapped history not found", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, listErr: fmt.Errorf("query: %w", storage.ErrNotFound), wantNotFound: true}, + {name: "log store failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, listErr: backendErr, wantCause: backendErr}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockCtrl := gomock.NewController(t) + factory := storagemock.NewMockFactory(mockCtrl) + stores := storagemock.NewMockStorage(mockCtrl) + uriStore := storagemock.NewMockRequestURIStore(mockCtrl) + logStore := storagemock.NewMockRequestLogStore(mockCtrl) + if !tt.wantInvalid { + factory.EXPECT().For(storage.Config{QueueName: tt.req.Queue}).Return(stores, tt.factoryErr) + if tt.factoryErr == nil { + stores.EXPECT().GetRequestURIStore().Return(uriStore) + uriStore.EXPECT().GetIDByURI(gomock.Any(), tt.req.URI).Return(tt.mappedID, tt.mappingErr) + if tt.mappingErr == nil { + stores.EXPECT().GetRequestLogStore().Return(logStore) + logStore.EXPECT().List(gomock.Any(), tt.mappedID).Return(logs, tt.listErr) + } + } + } + + core, observed := observer.New(zap.DebugLevel) + scope := tally.NewTestScope("test", nil) + controller := NewRequestHistoryController(zap.New(core).Sugar(), scope, factory) + ctx := metrics.WithContextTags(context.Background(), metrics.NewTag("queue", "context-queue")) + + got, err := controller.GetRequestHistoryByURI(ctx, tt.req) + + assert.Equal(t, tt.want, got) + if tt.wantInvalid { + assert.True(t, IsInvalidRequest(err)) + } + assert.Equal(t, tt.wantNotFound, IsRequestHistoryNotFound(err)) + assert.Equal(t, tt.wantInvalid || tt.wantNotFound, errs.IsUserError(err)) + if tt.wantCause != nil { + assert.ErrorIs(t, err, tt.wantCause) + } + if tt.want != nil { + require.NoError(t, err) + } else { + require.Error(t, err) + } + if tt.wantNotFound { + var notFound *RequestHistoryNotFoundError + require.ErrorAs(t, err, ¬Found) + assert.Empty(t, notFound.RequestID) + assert.Equal(t, uri, notFound.URI) + } + + entries := observed.FilterMessage("request history retrieved by URI").All() + if tt.wantLog { + require.Len(t, entries, 1) + assert.Equal(t, uri, entries[0].ContextMap()["uri"]) + assert.Equal(t, requestID, entries[0].ContextMap()["request_id"]) + assert.Equal(t, queue, entries[0].ContextMap()["queue"]) + assert.Equal(t, int64(len(logs)), entries[0].ContextMap()["event_count"]) + } else { + assert.Empty(t, entries) + } + + snapshot := scope.Snapshot() + start, ok := snapshot.Counters()["test.request_history_controller.get_by_uri.start+queue=context-queue"] + require.True(t, ok) + assert.EqualValues(t, 1, start.Value()) + assertOperationFinishIncludesContextTag(t, snapshot, "get_by_uri", err == nil) }) } } func TestRequestHistoryNotFoundError(t *testing.T) { - err := fmt.Errorf("lookup failed: %w", &RequestHistoryNotFoundError{RequestID: "request/queue/1"}) + tests := []struct { + name string + err error + want RequestHistoryNotFoundError + }{ + {name: "request ID", err: fmt.Errorf("lookup failed: %w", &RequestHistoryNotFoundError{RequestID: "request/queue/1"}), want: RequestHistoryNotFoundError{RequestID: "request/queue/1"}}, + {name: "URI", err: fmt.Errorf("lookup failed: %w", &RequestHistoryNotFoundError{URI: "git://repo/commit/1"}), want: RequestHistoryNotFoundError{URI: "git://repo/commit/1"}}, + } - assert.True(t, IsRequestHistoryNotFound(err)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.True(t, IsRequestHistoryNotFound(tt.err)) + var notFound *RequestHistoryNotFoundError + require.ErrorAs(t, tt.err, ¬Found) + assert.Equal(t, tt.want, *notFound) + }) + } assert.False(t, IsRequestHistoryNotFound(errors.New("other"))) - var notFound *RequestHistoryNotFoundError - require.ErrorAs(t, err, ¬Found) - assert.Equal(t, "request/queue/1", notFound.RequestID) } -func assertOperationFinishIncludesContextTag(t *testing.T, snapshot tally.Snapshot, success bool) { +func assertOperationFinishIncludesContextTag(t *testing.T, snapshot tally.Snapshot, operation string, success bool) { t.Helper() wantResult := "error" if success { wantResult = "success" } for _, histogram := range snapshot.Histograms() { - if histogram.Name() == "test.request_history_controller.get_by_id.finish" { + if histogram.Name() == "test.request_history_controller."+operation+".finish" { assert.Equal(t, "context-queue", histogram.Tags()["queue"]) assert.Equal(t, wantResult, histogram.Tags()["result"]) return From d99a91a2e6bb4c1d19b166d7ed3f4c355de162ee Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Thu, 3 Sep 2026 00:46:13 +0000 Subject: [PATCH 2/3] refactor(stovepipe): separate history URI read --- stovepipe/controller/request_history.go | 34 +++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/stovepipe/controller/request_history.go b/stovepipe/controller/request_history.go index d4adadb6..43219d4b 100644 --- a/stovepipe/controller/request_history.go +++ b/stovepipe/controller/request_history.go @@ -92,38 +92,46 @@ func (c *requestHistoryController) GetRequestHistoryByURI(ctx context.Context, r op := metrics.Begin(c.metricsScope, "get_by_uri", metrics.StorageLatencyBuckets, metrics.TagsFromContext(ctx)...) defer func() { op.Complete(retErr) }() + history, retErr := c.readHistoryByURI(ctx, req) + if retErr != nil { + return nil, retErr + } + c.logger.Debugw("request history retrieved by URI", + "uri", req.URI, + "request_id", history.RequestID, + "queue", req.Queue, + "event_count", len(history.Events), + ) + return []entity.RequestHistory{history}, nil +} + +func (c *requestHistoryController) readHistoryByURI(ctx context.Context, req entity.GetRequestHistoryByURIRequest) (entity.RequestHistory, error) { if err := validateHistoryIdentifier("queue", req.Queue); err != nil { - return nil, fmt.Errorf("GetRequestHistoryByURI invalid queue: %w", err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid queue: %w", err) } if err := validateHistoryIdentifier("URI", req.URI); err != nil { - return nil, fmt.Errorf("GetRequestHistoryByURI invalid request: %w", err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid request: %w", err) } stores, err := c.stores.For(storage.Config{QueueName: req.Queue}) if err != nil { - return nil, fmt.Errorf("GetRequestHistoryByURI failed to resolve storage for queue %q: %w", req.Queue, err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI failed to resolve storage for queue %q: %w", req.Queue, err) } requestID, err := stores.GetRequestURIStore().GetIDByURI(ctx, req.URI) if err != nil { if storage.IsNotFound(err) { - return nil, errs.NewUserError(&RequestHistoryNotFoundError{URI: req.URI}) + return entity.RequestHistory{}, errs.NewUserError(&RequestHistoryNotFoundError{URI: req.URI}) } - return nil, fmt.Errorf("GetRequestHistoryByURI failed to resolve request URI %s: %w", req.URI, err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI failed to resolve request URI %s: %w", req.URI, err) } logs, err := loadRequestLogs(ctx, stores.GetRequestLogStore(), requestID, &RequestHistoryNotFoundError{URI: req.URI}) if err != nil { - return nil, fmt.Errorf("GetRequestHistoryByURI failed to list request logs uri=%s request_id=%s: %w", req.URI, requestID, err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI failed to list request logs uri=%s request_id=%s: %w", req.URI, requestID, err) } - c.logger.Debugw("request history retrieved by URI", - "uri", req.URI, - "request_id", requestID, - "queue", req.Queue, - "event_count", len(logs), - ) - return []entity.RequestHistory{{RequestID: requestID, Events: logs}}, nil + return entity.RequestHistory{RequestID: requestID, Events: logs}, nil } func loadRequestLogs(ctx context.Context, store storage.RequestLogStore, requestID string, notFound *RequestHistoryNotFoundError) ([]entity.RequestLog, error) { From fe4e936840679bda8e15434ba4a46d230fdb6cb0 Mon Sep 17 00:00:00 2001 From: mnoah1 Date: Wed, 9 Sep 2026 16:10:29 +0000 Subject: [PATCH 3/3] fix(stovepipe): distinguish inconsistent history data --- stovepipe/controller/read_errors.go | 30 +++++++++----- stovepipe/controller/request_history.go | 24 +++++------ stovepipe/controller/request_history_test.go | 42 ++++++++++++++------ 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/stovepipe/controller/read_errors.go b/stovepipe/controller/read_errors.go index d81153c1..127fa436 100644 --- a/stovepipe/controller/read_errors.go +++ b/stovepipe/controller/read_errors.go @@ -41,22 +41,34 @@ func validateHistoryIdentifier(name, value string) error { return nil } -// RequestHistoryNotFoundError indicates that no retained history exists for a selector. -type RequestHistoryNotFoundError struct { +// RequestHistoryByIDNotFoundError indicates that no retained history exists for a request ID. +type RequestHistoryByIDNotFoundError struct { + // RequestID is the selected request identifier. RequestID string - URI string } // Error implements error. -func (e *RequestHistoryNotFoundError) Error() string { - if e.RequestID == "" { - return fmt.Sprintf("request history not found for URI %q", e.URI) - } +func (e *RequestHistoryByIDNotFoundError) Error() string { return fmt.Sprintf("request history not found for request ID %q", e.RequestID) } +// RequestHistoryByURINotFoundError indicates that no retained history exists for a URI. +type RequestHistoryByURINotFoundError struct { + // URI is the selected commit URI. + URI string +} + +// Error implements error. +func (e *RequestHistoryByURINotFoundError) Error() string { + return fmt.Sprintf("request history not found for URI %q", e.URI) +} + // IsRequestHistoryNotFound reports whether err contains a retained-history absence. func IsRequestHistoryNotFound(err error) bool { - var target *RequestHistoryNotFoundError - return errors.As(err, &target) + var byID *RequestHistoryByIDNotFoundError + if errors.As(err, &byID) { + return true + } + var byURI *RequestHistoryByURINotFoundError + return errors.As(err, &byURI) } diff --git a/stovepipe/controller/request_history.go b/stovepipe/controller/request_history.go index 43219d4b..cc79cb97 100644 --- a/stovepipe/controller/request_history.go +++ b/stovepipe/controller/request_history.go @@ -79,8 +79,11 @@ func (c *requestHistoryController) readHistoryByID(ctx context.Context, req enti return nil, fmt.Errorf("GetRequestHistoryByID failed to resolve storage for queue %q: %w", req.Queue, err) } - logs, err := loadRequestLogs(ctx, stores.GetRequestLogStore(), req.ID, &RequestHistoryNotFoundError{RequestID: req.ID}) + logs, err := stores.GetRequestLogStore().List(ctx, req.ID) if err != nil { + if storage.IsNotFound(err) { + return nil, errs.NewUserError(&RequestHistoryByIDNotFoundError{RequestID: req.ID}) + } return nil, fmt.Errorf("GetRequestHistoryByID failed to list request logs request_id=%s: %w", req.ID, err) } @@ -107,10 +110,10 @@ func (c *requestHistoryController) GetRequestHistoryByURI(ctx context.Context, r func (c *requestHistoryController) readHistoryByURI(ctx context.Context, req entity.GetRequestHistoryByURIRequest) (entity.RequestHistory, error) { if err := validateHistoryIdentifier("queue", req.Queue); err != nil { - return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid queue: %w", err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid queue=%q: %w", req.Queue, err) } if err := validateHistoryIdentifier("URI", req.URI); err != nil { - return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid request: %w", err) + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI invalid uri=%q queue=%q: %w", req.URI, req.Queue, err) } stores, err := c.stores.For(storage.Config{QueueName: req.Queue}) @@ -121,23 +124,18 @@ func (c *requestHistoryController) readHistoryByURI(ctx context.Context, req ent requestID, err := stores.GetRequestURIStore().GetIDByURI(ctx, req.URI) if err != nil { if storage.IsNotFound(err) { - return entity.RequestHistory{}, errs.NewUserError(&RequestHistoryNotFoundError{URI: req.URI}) + return entity.RequestHistory{}, errs.NewUserError(&RequestHistoryByURINotFoundError{URI: req.URI}) } return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI failed to resolve request URI %s: %w", req.URI, err) } - logs, err := loadRequestLogs(ctx, stores.GetRequestLogStore(), requestID, &RequestHistoryNotFoundError{URI: req.URI}) + logs, err := stores.GetRequestLogStore().List(ctx, requestID) if err != nil { + if storage.IsNotFound(err) { + return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI found URI mapping without retained request logs uri=%q request_id=%q: %w", req.URI, requestID, err) + } return entity.RequestHistory{}, fmt.Errorf("GetRequestHistoryByURI failed to list request logs uri=%s request_id=%s: %w", req.URI, requestID, err) } return entity.RequestHistory{RequestID: requestID, Events: logs}, nil } - -func loadRequestLogs(ctx context.Context, store storage.RequestLogStore, requestID string, notFound *RequestHistoryNotFoundError) ([]entity.RequestLog, error) { - logs, err := store.List(ctx, requestID) - if storage.IsNotFound(err) { - return nil, errs.NewUserError(notFound) - } - return logs, err -} diff --git a/stovepipe/controller/request_history_test.go b/stovepipe/controller/request_history_test.go index dc37d4d0..866d41a8 100644 --- a/stovepipe/controller/request_history_test.go +++ b/stovepipe/controller/request_history_test.go @@ -102,6 +102,11 @@ func TestGetRequestHistoryByID(t *testing.T) { } assert.Equal(t, tt.wantNotFound, IsRequestHistoryNotFound(err)) assert.Equal(t, tt.wantUser, errs.IsUserError(err)) + if tt.wantNotFound { + var notFound *RequestHistoryByIDNotFoundError + require.ErrorAs(t, err, ¬Found) + assert.Equal(t, requestID, notFound.RequestID) + } if tt.wantCause != nil { assert.ErrorIs(t, err, tt.wantCause) } @@ -165,7 +170,7 @@ func TestGetRequestHistoryByURI(t *testing.T) { {name: "storage factory failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, factoryErr: backendErr, wantCause: backendErr}, {name: "URI mapping not found", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappingErr: fmt.Errorf("lookup: %w", storage.ErrNotFound), wantNotFound: true}, {name: "URI store failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappingErr: backendErr, wantCause: backendErr}, - {name: "mapped history not found", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, listErr: fmt.Errorf("query: %w", storage.ErrNotFound), wantNotFound: true}, + {name: "mapped history absence is internal", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, listErr: fmt.Errorf("query: %w", storage.ErrNotFound), wantCause: storage.ErrNotFound}, {name: "log store failure", req: entity.GetRequestHistoryByURIRequest{Queue: queue, URI: uri}, mappedID: requestID, listErr: backendErr, wantCause: backendErr}, } @@ -210,9 +215,8 @@ func TestGetRequestHistoryByURI(t *testing.T) { require.Error(t, err) } if tt.wantNotFound { - var notFound *RequestHistoryNotFoundError + var notFound *RequestHistoryByURINotFoundError require.ErrorAs(t, err, ¬Found) - assert.Empty(t, notFound.RequestID) assert.Equal(t, uri, notFound.URI) } @@ -236,22 +240,36 @@ func TestGetRequestHistoryByURI(t *testing.T) { } } -func TestRequestHistoryNotFoundError(t *testing.T) { +func TestRequestHistoryNotFoundErrors(t *testing.T) { tests := []struct { - name string - err error - want RequestHistoryNotFoundError + name string + err error + assert func(*testing.T, error) }{ - {name: "request ID", err: fmt.Errorf("lookup failed: %w", &RequestHistoryNotFoundError{RequestID: "request/queue/1"}), want: RequestHistoryNotFoundError{RequestID: "request/queue/1"}}, - {name: "URI", err: fmt.Errorf("lookup failed: %w", &RequestHistoryNotFoundError{URI: "git://repo/commit/1"}), want: RequestHistoryNotFoundError{URI: "git://repo/commit/1"}}, + { + name: "request ID", + err: fmt.Errorf("lookup failed: %w", &RequestHistoryByIDNotFoundError{RequestID: "request/queue/1"}), + assert: func(t *testing.T, err error) { + var notFound *RequestHistoryByIDNotFoundError + require.ErrorAs(t, err, ¬Found) + assert.Equal(t, "request/queue/1", notFound.RequestID) + }, + }, + { + name: "URI", + err: fmt.Errorf("lookup failed: %w", &RequestHistoryByURINotFoundError{URI: "git://repo/commit/1"}), + assert: func(t *testing.T, err error) { + var notFound *RequestHistoryByURINotFoundError + require.ErrorAs(t, err, ¬Found) + assert.Equal(t, "git://repo/commit/1", notFound.URI) + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { assert.True(t, IsRequestHistoryNotFound(tt.err)) - var notFound *RequestHistoryNotFoundError - require.ErrorAs(t, tt.err, ¬Found) - assert.Equal(t, tt.want, *notFound) + tt.assert(t, tt.err) }) } assert.False(t, IsRequestHistoryNotFound(errors.New("other")))