mirror of
https://gitea.com/gitea/tea.git
synced 2026-10-26 11:01:25 +01:00
secret set and variable set read the value from -b/--body, -F/--body-file or standard input when it is not a terminal (one trailing line break removed), and otherwise prompt for it; --env-file sets every NAME=value of a dotenv file. --value and --value-file keep working. The scope flags are -o/--org and --account on each command, and secret list no longer offers --account, which Gitea cannot list. Commands report what they did, naming the scope and the name Gitea stores: "Set Actions secret LOWER_NAME for alice/x (names are upper-cased)"; --json returns the stored name and omits an unknown creation time. Deleting asks "Delete secret X from alice/x?". Lists show NAME, CREATED and DESCRIPTION for secrets and NAME, VALUE and DESCRIPTION for variables; variable view shows a record and the new variable get prints the raw value. A missing secret or variable is reported by name and scope. The global --user selects the account tea acts as, unlike gh's --user, so secret and variable add a note on a terminal pointing to --account when it is given, as run list does for --actor. Help explains the scopes. Signed-off-by: bircni <bircni@icloud.com>
179 lines
7.7 KiB
Go
179 lines
7.7 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dev/tea/internal/clierr"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestSecretValueFileDropsTrailingLineEnding(t *testing.T) {
|
|
var stored string
|
|
server := newAuthenticatedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
switch r.URL.Path {
|
|
case "/api/v1/repos/alice/project/actions/secrets/BUILD_TOKEN":
|
|
var body struct{ Data string }
|
|
require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
|
|
stored = body.Data
|
|
w.WriteHeader(http.StatusNoContent)
|
|
default:
|
|
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
deps, _, _ := serverDeps(t, server.URL)
|
|
deps.Streams.In = strings.NewReader("sensitive\r\n")
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"secret", "set", "BUILD_TOKEN", "-R", "alice/project", "--value-file", "-", "--yes"}))
|
|
require.Equal(t, "sensitive", stored)
|
|
}
|
|
|
|
func TestActionValueSetReadsValuesAndReportsStoredNames(t *testing.T) {
|
|
envFile := filepath.Join(t.TempDir(), ".env")
|
|
require.NoError(t, os.WriteFile(envFile, []byte("# deploy\nAPI_KEY=abc\nexport region=\"eu\\nwest\"\nMODE=fast # inline\n"), 0o600))
|
|
for _, tc := range []struct {
|
|
name string
|
|
args []string
|
|
stdin string
|
|
requests []string
|
|
out, err string
|
|
}{
|
|
{
|
|
"lower-case name from --body",
|
|
[]string{"secret", "set", "lower_name", "-b", "x"},
|
|
"",
|
|
[]string{`PUT actions/secrets/lower_name {"data":"x","description":""}`},
|
|
"",
|
|
"✓ Set Actions secret LOWER_NAME for alice/project (names are upper-cased)\n",
|
|
},
|
|
{
|
|
"standard input",
|
|
[]string{"secret", "set", "TOKEN"},
|
|
"piped\n",
|
|
[]string{`PUT actions/secrets/TOKEN {"data":"piped","description":""}`},
|
|
"", "✓ Set Actions secret TOKEN for alice/project\n",
|
|
},
|
|
{
|
|
"JSON names the stored secret",
|
|
[]string{"secret", "set", "token", "--body", "x", "--json"},
|
|
"",
|
|
[]string{`PUT actions/secrets/token {"data":"x","description":""}`},
|
|
`{"name":"TOKEN","description":""}`, "",
|
|
},
|
|
{
|
|
"organization",
|
|
[]string{"secret", "set", "TOKEN", "-o", "acme", "-b", "x"},
|
|
"",
|
|
[]string{`PUT api/v1/orgs/acme/actions/secrets/TOKEN {"data":"x","description":""}`},
|
|
"", "✓ Set Actions secret TOKEN for organization acme\n",
|
|
},
|
|
{
|
|
"account",
|
|
[]string{"secret", "set", "TOKEN", "--account", "-b", "x"},
|
|
"",
|
|
[]string{`PUT api/v1/user/actions/secrets/TOKEN {"data":"x","description":""}`},
|
|
"", "✓ Set Actions secret TOKEN for your account\n",
|
|
},
|
|
{
|
|
"dotenv file",
|
|
[]string{"variable", "set", "--env-file", envFile, "-o", "acme"},
|
|
"",
|
|
[]string{
|
|
"GET api/v1/orgs/acme/actions/variables/API_KEY", `POST api/v1/orgs/acme/actions/variables/API_KEY {"value":"abc","description":""}`,
|
|
"GET api/v1/orgs/acme/actions/variables/region", `POST api/v1/orgs/acme/actions/variables/region {"value":"eu\nwest","description":""}`,
|
|
"GET api/v1/orgs/acme/actions/variables/MODE", `POST api/v1/orgs/acme/actions/variables/MODE {"value":"fast","description":""}`,
|
|
},
|
|
"",
|
|
"✓ Set Actions variable API_KEY for organization acme\n✓ Set Actions variable REGION for organization acme (names are upper-cased)\n✓ Set Actions variable MODE for organization acme\n",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
server, requests := actionsAPI(t, map[string]string{
|
|
"PUT actions/secrets/lower_name": "204 ", "PUT actions/secrets/TOKEN": "204 ", "PUT actions/secrets/token": "204 ",
|
|
"PUT api/v1/orgs/acme/actions/secrets/TOKEN": "204 ", "PUT api/v1/user/actions/secrets/TOKEN": "204 ",
|
|
"GET api/v1/orgs/acme/actions/variables/API_KEY": `404 {}`, "POST api/v1/orgs/acme/actions/variables/API_KEY": "201 ",
|
|
"GET api/v1/orgs/acme/actions/variables/region": `404 {}`, "POST api/v1/orgs/acme/actions/variables/region": "201 ",
|
|
"GET api/v1/orgs/acme/actions/variables/MODE": `404 {}`, "POST api/v1/orgs/acme/actions/variables/MODE": "201 ",
|
|
})
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
|
deps.Streams.In = strings.NewReader(tc.stdin)
|
|
args := tc.args
|
|
if !strings.Contains(strings.Join(args, " "), "-o acme") && !strings.Contains(strings.Join(args, " "), "--account") {
|
|
args = append(args, "-R", "alice/project")
|
|
}
|
|
require.NoError(t, Execute(context.Background(), deps, args))
|
|
require.Equal(t, tc.requests, *requests)
|
|
if tc.out != "" {
|
|
require.JSONEq(t, tc.out, out.String())
|
|
} else {
|
|
require.Empty(t, out.String())
|
|
}
|
|
require.Equal(t, tc.err, diagnostics.String())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestActionValueListsShowScopeAndExplainTheAccountFlags(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{
|
|
"GET actions/secrets": `[{"name":"DEPLOY_TOKEN","description":"Deploy token","created_at":"2028-01-01T10:00:00Z"}]`,
|
|
"GET actions/variables": `[{"name":"ENVIRONMENT","data":"staging","description":"Target environment"}]`,
|
|
})
|
|
deps, out, diagnostics := actionsDeps(t, server.URL, true)
|
|
deps.Streams.ErrTTY = true
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"secret", "list", "-R", "alice/project", "--user", "alice"}))
|
|
require.Equal(t, "Showing 1 of 1 secrets in alice/project\n\nNAME CREATED DESCRIPTION\nDEPLOY_TOKEN about 2 hours ago Deploy token\n", out.String())
|
|
require.Equal(t, "note: --user selects the account tea acts as (alice); Gitea cannot list your account's secrets; set or delete them with --account\n", diagnostics.String())
|
|
out.Reset()
|
|
require.NoError(t, Execute(context.Background(), deps, []string{"variable", "list", "-R", "alice/project"}))
|
|
require.Equal(t, "Showing 1 of 1 variables in alice/project\n\nNAME VALUE DESCRIPTION\nENVIRONMENT staging Target environment\n", out.String())
|
|
err := Execute(context.Background(), deps, []string{"secret", "list", "--account"})
|
|
require.Equal(t, clierr.Usage, clierr.ExitCode(err))
|
|
require.ErrorContains(t, err, "unknown flag: --account")
|
|
}
|
|
|
|
func TestVariableGetAndView(t *testing.T) {
|
|
server, _ := actionsAPI(t, map[string]string{"GET actions/variables/region": `{"name":"REGION","data":"eu\nwest","description":"Where to deploy"}`})
|
|
for _, tc := range []struct {
|
|
args []string
|
|
tty bool
|
|
want string
|
|
}{
|
|
{[]string{"variable", "get", "region"}, false, "eu\nwest\n"},
|
|
{[]string{"variable", "view", "region"}, true, "REGION\nScope: alice/project\nDescription: Where to deploy\n\neu\nwest\n"},
|
|
{[]string{"variable", "view", "region"}, false, "title:\tREGION\nscope:\talice/project\ndescription:\tWhere to deploy\n--\neu\nwest\n"},
|
|
} {
|
|
deps, out, _ := actionsDeps(t, server.URL, tc.tty)
|
|
require.NoError(t, Execute(context.Background(), deps, append(tc.args, "-R", "alice/project")))
|
|
require.Equal(t, tc.want, out.String(), "%v tty=%t", tc.args, tc.tty)
|
|
}
|
|
}
|
|
|
|
func TestActionValueDeleteConfirmsWithTheScope(t *testing.T) {
|
|
server, requests := actionsAPI(t, map[string]string{"DELETE actions/secrets/deploy_token": "204 "})
|
|
for _, accept := range []bool{false, true} {
|
|
*requests = nil
|
|
deps, _, diagnostics := actionsDeps(t, server.URL, true)
|
|
prompter := &scriptedPrompter{accept: accept}
|
|
deps.Prompter = prompter
|
|
deps.Streams.InTTY, deps.Streams.ErrTTY = true, true
|
|
err := Execute(context.Background(), deps, []string{"secret", "delete", "deploy_token", "-R", "alice/project"})
|
|
require.Equal(t, []string{"Delete secret DEPLOY_TOKEN from alice/project?"}, prompter.confirmations)
|
|
if !accept {
|
|
require.Equal(t, clierr.Canceled, clierr.ExitCode(err))
|
|
require.Empty(t, *requests)
|
|
continue
|
|
}
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{"DELETE actions/secrets/deploy_token"}, *requests)
|
|
require.Equal(t, "✓ Deleted Actions secret DEPLOY_TOKEN from alice/project\n", diagnostics.String())
|
|
}
|
|
}
|