mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 19:11:25 +01:00
fix: address verified review findings (actions-workflows-values-1)
Signed-off-by: bircni <bircni@icloud.com>
This commit is contained in:
12 files changed
+268
-34
No files matched your search
@@ -36,12 +36,21 @@ func (s ActionScope) String() string {
|
||||
type ActionSecret struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
CreatedAt string `json:"created_at,omitempty"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// ActionValueName is the name Gitea stores a secret or variable under: names are upper-cased.
|
||||
func ActionValueName(name string) string { return strings.ToUpper(name) }
|
||||
|
||||
// classifyValue classifies err as a missing secret or variable only when the 404 says so: Gitea
|
||||
// answers a plain "not found" when the repository or organization itself does not exist.
|
||||
func classifyValue(err error, kind, name string, scope ActionScope) error {
|
||||
if apiErr, ok := errors.AsType[*transport.APIError](err); ok && apiErr.Status == http.StatusNotFound && !strings.Contains(apiErr.Message, kind) {
|
||||
return err
|
||||
}
|
||||
return Classify(err, kind, ActionValueName(name), scope.String())
|
||||
}
|
||||
|
||||
func secretFrom(secret *gitea.Secret) ActionSecret {
|
||||
return ActionSecret{Name: secret.Name, Description: secret.Description, CreatedAt: timestamp(secret.Created)}
|
||||
}
|
||||
@@ -117,7 +126,7 @@ func (s ActionSecrets) Delete(ctx context.Context, name string) error {
|
||||
// TODO(sdk): DeleteRepoSecret does not escape the secret name.
|
||||
return c.Actions.DeleteRepoSecret(ctx, owner, repo, url.PathEscape(name))
|
||||
})
|
||||
return Classify(err, "secret", ActionValueName(name), s.Scope.String())
|
||||
return classifyValue(err, "secret", name, s.Scope)
|
||||
}
|
||||
|
||||
// ActionVariables manages scoped non-secret Actions variables.
|
||||
@@ -171,23 +180,28 @@ func (s ActionVariables) View(ctx context.Context, name string) (ActionVariable,
|
||||
err = getJSON(ctx, s.Client, s.repoVariables(name), variable)
|
||||
}
|
||||
if err != nil {
|
||||
return ActionVariable{}, Classify(err, "variable", ActionValueName(name), s.Scope.String())
|
||||
return ActionVariable{}, classifyValue(err, "variable", name, s.Scope)
|
||||
}
|
||||
return variableFrom(variable), nil
|
||||
}
|
||||
|
||||
// Set chooses create or update from an authoritative read.
|
||||
func (s ActionVariables) Set(ctx context.Context, name, value, description string) (ActionVariable, error) {
|
||||
// Set chooses create or update from an authoritative read. A nil description keeps the one of an
|
||||
// existing variable, as Gitea replaces it on every update.
|
||||
func (s ActionVariables) Set(ctx context.Context, name, value string, newDescription *string) (ActionVariable, error) {
|
||||
exists := true
|
||||
if _, err := s.View(ctx, name); err != nil {
|
||||
current, err := s.View(ctx, name)
|
||||
if err != nil {
|
||||
if !errors.As(err, new(*NotFoundError)) {
|
||||
return ActionVariable{}, err
|
||||
}
|
||||
exists = false
|
||||
}
|
||||
description := current.Description
|
||||
if newDescription != nil {
|
||||
description = *newDescription
|
||||
}
|
||||
create := gitea.CreateActionsVariableOption{Value: value, Description: description}
|
||||
update := gitea.UpdateActionsVariableOption{Name: name, Value: value, Description: description}
|
||||
var err error
|
||||
switch {
|
||||
case s.Scope.Account:
|
||||
err = do(ctx, s.Client, func(ctx context.Context, c *gitea.Client) (*gitea.Response, error) {
|
||||
@@ -228,5 +242,5 @@ func (s ActionVariables) Delete(ctx context.Context, name string) error {
|
||||
default:
|
||||
err = sendJSON(ctx, s.Client, http.MethodDelete, s.repoVariables(name), nil, nil)
|
||||
}
|
||||
return Classify(err, "variable", ActionValueName(name), s.Scope.String())
|
||||
return classifyValue(err, "variable", name, s.Scope)
|
||||
}
|
||||
@@ -6,8 +6,10 @@ package services
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.dev/tea/internal/transport"
|
||||
@@ -37,7 +39,7 @@ func TestVariableSetSelectsCreateOrUpdate(t *testing.T) {
|
||||
assert.Equal(t, scope.path+"/variables/BUILD_MODE", r.URL.Path)
|
||||
if r.Method == http.MethodGet {
|
||||
w.WriteHeader(test.read)
|
||||
_, _ = w.Write([]byte(`{"name":"BUILD_MODE","data":"old"}`))
|
||||
_, _ = w.Write([]byte(`{"name":"BUILD_MODE","data":"old","message":"variable not found"}`))
|
||||
return
|
||||
}
|
||||
mutations = append(mutations, r.Method)
|
||||
@@ -47,7 +49,7 @@ func TestVariableSetSelectsCreateOrUpdate(t *testing.T) {
|
||||
assert.Equal(t, "build flavor", payload["description"])
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
item, err := (ActionVariables{Client: client, Scope: scope.scope}).Set(context.Background(), "BUILD_MODE", "new\nvalue", "build flavor")
|
||||
item, err := (ActionVariables{Client: client, Scope: scope.scope}).Set(context.Background(), "BUILD_MODE", "new\nvalue", new("build flavor"))
|
||||
if test.wantMethod == "" {
|
||||
var apiErr *transport.APIError
|
||||
require.ErrorAs(t, err, &apiErr)
|
||||
@@ -139,3 +141,62 @@ func TestSecretListReturnsMetadataOnly(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVariableSetKeepsTheDescriptionUnlessGiven(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
description *string
|
||||
want string
|
||||
}{{"omitted", nil, "build flavor"}, {"given", new("other"), "other"}, {"cleared", new(""), ""}} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
var sent string
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == http.MethodGet {
|
||||
_, _ = w.Write([]byte(`{"name":"MODE","data":"old","description":"build flavor"}`))
|
||||
return
|
||||
}
|
||||
var payload map[string]string
|
||||
assert.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
|
||||
sent = payload["description"]
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
item, err := (ActionVariables{Client: client, Scope: ActionScope{Organization: "team"}}).Set(context.Background(), "MODE", "new", test.description)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.want, sent)
|
||||
require.Equal(t, test.want, item.Description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingScopeIsNotReportedAsAMissingValue(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
message string
|
||||
missing bool
|
||||
}{{"not found", false}, {"secret was not found [name: ]", true}, {"variable not found", true}} {
|
||||
t.Run(test.message, func(t *testing.T) {
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message":"` + test.message + `"}`))
|
||||
}))
|
||||
scope := ActionScope{Organization: "acme"}
|
||||
for name, err := range map[string]error{
|
||||
"secret": (ActionSecrets{Client: client, Scope: scope}).Delete(context.Background(), "TOKEN"),
|
||||
"variable": (ActionVariables{Client: client, Scope: scope}).Delete(context.Background(), "MODE"),
|
||||
} {
|
||||
var missing *NotFoundError
|
||||
require.Error(t, err)
|
||||
if strings.Contains(test.message, name) {
|
||||
require.ErrorAs(t, err, &missing, name)
|
||||
} else {
|
||||
require.False(t, errors.As(err, &missing), name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretSetResultHasEveryJSONField(t *testing.T) {
|
||||
data, err := json.Marshal(ActionSecret{Name: "TOKEN"})
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, `{"name":"TOKEN","description":"","created_at":""}`, string(data))
|
||||
}
|
||||
@@ -121,6 +121,12 @@ func (s Actions) Dispatch(ctx context.Context, id, ref string, inputs map[string
|
||||
return c.Actions.DispatchRepoWorkflow(ctx, owner, repo, id, gitea.CreateActionsWorkflowDispatchOption{Ref: ref, Inputs: inputs}, true)
|
||||
})
|
||||
if api, ok := errors.AsType[*transport.APIError](err); ok && api.Status == http.StatusNotFound && strings.Contains(api.Message, "ref") {
|
||||
// Gitea resolves a bare ref as a branch only; a tag needs its full name.
|
||||
if !strings.HasPrefix(ref, "refs/") {
|
||||
if retried, retryErr := s.Dispatch(ctx, id, "refs/tags/"+ref, inputs); retryErr == nil {
|
||||
return retried, nil
|
||||
}
|
||||
}
|
||||
return DispatchResult{}, &NotFoundError{Kind: "ref", Key: strconv.Quote(ref), Scope: s.Repository, Err: err}
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -336,3 +336,29 @@ func TestWorkflowFileTellsAMissingRefFromAMissingFile(t *testing.T) {
|
||||
_, err = service.WorkflowFile(context.Background(), workflow, "main")
|
||||
require.EqualError(t, err, "workflow file .gitea/workflows/ci.yml not found in owner/repo")
|
||||
}
|
||||
|
||||
func TestDispatchFallsBackToATagRef(t *testing.T) {
|
||||
var refs []string
|
||||
client := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var payload struct {
|
||||
Ref string `json:"ref"`
|
||||
}
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&payload))
|
||||
refs = append(refs, payload.Ref)
|
||||
if payload.Ref == "v1.0" || strings.HasSuffix(payload.Ref, "nope") {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
_, _ = w.Write([]byte(`{"message":"ref \"` + payload.Ref + `\" not found"}`))
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte(`{"workflow_run_id":12}`))
|
||||
}))
|
||||
service := Actions{Client: client, Repository: "owner/repo"}
|
||||
result, err := service.Dispatch(context.Background(), "ci.yml", "v1.0", nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int64(12), result.ID)
|
||||
_, err = service.Dispatch(context.Background(), "ci.yml", "nope", nil)
|
||||
var missing *NotFoundError
|
||||
require.ErrorAs(t, err, &missing)
|
||||
require.Equal(t, "ref", missing.Kind)
|
||||
require.Equal(t, []string{"v1.0", "refs/tags/v1.0", "nope", "refs/tags/nope"}, refs)
|
||||
}
|
||||
Reference in new issue
Block a user