feat(services): classify API failures by resource and report page totals

Add NotFoundError, ConflictError and AmbiguousError so commands can report
"issue #999 not found in alice/project" or "alice/old-stuff is archived"
instead of the transport's status text. Classify wraps a failure with the
resource the caller names; ClassifyRequest infers it from the endpoint for
failures no service classified, and maps the refusals Gitea explains (archived
repository, merged or draft pull request, own review, existing resource,
unknown user, disabled workflow, completed run, reused SSH key).

Page reports the X-Total-Count that listWindow discarded, NameOrID resolves a
resource by ID or name (numeric values are IDs first, ambiguous names list
their IDs), and rawPage pages raw collection endpoints.

Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
bircni committed 2026-10-11 22:48:49 +02:00
1 parent 0254c0509e
commit c8073f1b2e
5 files changed
+603 -5

No files matched your search

+37
View File
@@ -12,6 +12,7 @@ import (
"gitea.dev/tea/internal/transport"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// newTestClient serves handler for the duration of the test and returns a client for it.
@@ -38,3 +39,39 @@ func requestJSON(t *testing.T, r *http.Request) map[string]any {
assert.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
return payload
}
func TestNameOrIDPrefersIDsThenExactThenFoldedNames(t *testing.T) {
type label struct {
id int64
name string
}
labels := []label{{11, "bug"}, {12, "Bug"}, {13, "7"}, {7, "seven"}, {20, "kind/feature"}, {21, "dup"}, {22, "dup"}}
lookup := NameOrID[label]{Kind: "label", Scope: "alice/project", ID: func(l label) int64 { return l.id }, Name: func(l label) string { return l.name }}
for _, tc := range []struct {
raw string
byName bool
want int64
err string
}{
{raw: "7", want: 7},
{raw: "7", byName: true, want: 13},
{raw: "13", want: 13},
{raw: "bug", want: 11},
{raw: "BUG", err: `label "BUG" is ambiguous in alice/project (IDs 11, 12); pass an ID`},
{raw: "KIND/FEATURE", want: 20},
{raw: " kind/feature ", want: 20},
{raw: "dup", err: `label "dup" is ambiguous in alice/project (IDs 21, 22); pass an ID`},
{raw: "nope", err: `label "nope" not found in alice/project`},
{raw: "99", err: "label 99 not found in alice/project"},
{raw: "99", byName: true, err: `label "99" not found in alice/project`},
} {
lookup.ByName = tc.byName
got, err := lookup.Find(labels, tc.raw)
if tc.err != "" {
require.EqualError(t, err, tc.err, tc.raw)
continue
}
require.NoError(t, err, tc.raw)
require.Equal(t, tc.want, got.id, tc.raw)
}
}