mirror of
https://gitea.com/gitea/tea.git
synced 2025-10-20 14:34:05 +02:00

## Summary This PR adds comprehensive Actions secrets and variables management functionality to the tea CLI, enabling users to manage their repository's CI/CD configuration directly from the command line. ## Features Added ### Actions Secrets Management - **List secrets**: `tea actions secrets list` - Display all repository action secrets - **Create secrets**: `tea actions secrets create <name>` - Create new secrets with interactive prompts - **Delete secrets**: `tea actions secrets delete <name>` - Remove existing secrets ### Actions Variables Management - **List variables**: `tea actions variables list` - Display all repository action variables - **Set variables**: `tea actions variables set <name> <value>` - Create or update variables - **Delete variables**: `tea actions variables delete <name>` - Remove existing variables ## Implementation Details - **Interactive prompts**: Secure input handling for sensitive secret values - **Input validation**: Proper validation for secret/variable names and values - **Table formatting**: Consistent output formatting with existing tea commands - **Error handling**: Comprehensive error handling and user feedback - **Test coverage**: Full test suite for all functionality ## Usage Examples ```bash # Secrets management tea actions secrets list tea actions secrets create API_KEY # Will prompt securely for value tea actions secrets delete OLD_SECRET # Variables management tea actions variables list tea actions variables set API_URL https://api.example.com tea actions variables delete UNUSED_VAR ``` ## Related Issue Resolves #797 ## Testing - All new functionality includes comprehensive unit tests - Integration with existing tea CLI patterns and conventions - Validated against Gitea Actions API Reviewed-on: https://gitea.com/gitea/tea/pulls/796 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Ross Golder <ross@golder.org> Co-committed-by: Ross Golder <ross@golder.org>
214 lines
5 KiB
Go
214 lines
5 KiB
Go
// Copyright 2025 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package print
|
|
|
|
import (
|
|
"bytes"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"code.gitea.io/sdk/gitea"
|
|
)
|
|
|
|
func TestActionSecretsListEmpty(t *testing.T) {
|
|
// Test with empty secrets - should not panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionSecretsList panicked with empty list: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionSecretsList([]*gitea.Secret{}, "")
|
|
}
|
|
|
|
func TestActionSecretsListWithData(t *testing.T) {
|
|
secrets := []*gitea.Secret{
|
|
{
|
|
Name: "TEST_SECRET_1",
|
|
Created: time.Now().Add(-24 * time.Hour),
|
|
},
|
|
{
|
|
Name: "TEST_SECRET_2",
|
|
Created: time.Now().Add(-48 * time.Hour),
|
|
},
|
|
}
|
|
|
|
// Test that it doesn't panic with real data
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionSecretsList panicked with data: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionSecretsList(secrets, "")
|
|
|
|
// Test JSON output format to verify structure
|
|
var buf bytes.Buffer
|
|
testTable := table{
|
|
headers: []string{"Name", "Created"},
|
|
}
|
|
|
|
for _, secret := range secrets {
|
|
testTable.addRow(secret.Name, FormatTime(secret.Created, true))
|
|
}
|
|
|
|
testTable.fprint(&buf, "json")
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "TEST_SECRET_1") {
|
|
t.Error("Expected TEST_SECRET_1 in JSON output")
|
|
}
|
|
if !strings.Contains(output, "TEST_SECRET_2") {
|
|
t.Error("Expected TEST_SECRET_2 in JSON output")
|
|
}
|
|
}
|
|
|
|
func TestActionVariableDetails(t *testing.T) {
|
|
variable := &gitea.RepoActionVariable{
|
|
Name: "TEST_VARIABLE",
|
|
Value: "test_value",
|
|
RepoID: 123,
|
|
OwnerID: 456,
|
|
}
|
|
|
|
// Test that it doesn't panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionVariableDetails panicked: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionVariableDetails(variable)
|
|
}
|
|
|
|
func TestActionVariablesListEmpty(t *testing.T) {
|
|
// Test with empty variables - should not panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionVariablesList panicked with empty list: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionVariablesList([]*gitea.RepoActionVariable{}, "")
|
|
}
|
|
|
|
func TestActionVariablesListWithData(t *testing.T) {
|
|
variables := []*gitea.RepoActionVariable{
|
|
{
|
|
Name: "TEST_VARIABLE_1",
|
|
Value: "short_value",
|
|
RepoID: 123,
|
|
OwnerID: 456,
|
|
},
|
|
{
|
|
Name: "TEST_VARIABLE_2",
|
|
Value: strings.Repeat("a", 60), // Long value to test truncation
|
|
RepoID: 124,
|
|
OwnerID: 457,
|
|
},
|
|
}
|
|
|
|
// Test that it doesn't panic with real data
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionVariablesList panicked with data: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionVariablesList(variables, "")
|
|
|
|
// Test JSON output format to verify structure and truncation
|
|
var buf bytes.Buffer
|
|
testTable := table{
|
|
headers: []string{"Name", "Value", "Repository ID"},
|
|
}
|
|
|
|
for _, variable := range variables {
|
|
value := variable.Value
|
|
if len(value) > 50 {
|
|
value = value[:47] + "..."
|
|
}
|
|
testTable.addRow(variable.Name, value, strconv.Itoa(int(variable.RepoID)))
|
|
}
|
|
|
|
testTable.fprint(&buf, "json")
|
|
output := buf.String()
|
|
|
|
if !strings.Contains(output, "TEST_VARIABLE_1") {
|
|
t.Error("Expected TEST_VARIABLE_1 in JSON output")
|
|
}
|
|
if !strings.Contains(output, "TEST_VARIABLE_2") {
|
|
t.Error("Expected TEST_VARIABLE_2 in JSON output")
|
|
}
|
|
|
|
// Check that long value is truncated in our test table
|
|
if strings.Contains(output, strings.Repeat("a", 60)) {
|
|
t.Error("Long value should be truncated in table output")
|
|
}
|
|
}
|
|
|
|
func TestActionVariablesListValueTruncation(t *testing.T) {
|
|
variable := &gitea.RepoActionVariable{
|
|
Name: "LONG_VALUE_VARIABLE",
|
|
Value: strings.Repeat("abcdefghij", 10), // 100 characters
|
|
RepoID: 123,
|
|
OwnerID: 456,
|
|
}
|
|
|
|
// Test that it doesn't panic
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("ActionVariablesList panicked with long value: %v", r)
|
|
}
|
|
}()
|
|
|
|
ActionVariablesList([]*gitea.RepoActionVariable{variable}, "")
|
|
|
|
// Test the truncation logic directly
|
|
value := variable.Value
|
|
if len(value) > 50 {
|
|
value = value[:47] + "..."
|
|
}
|
|
|
|
if len(value) != 50 { // 47 chars + "..." = 50
|
|
t.Errorf("Truncated value should be 50 characters, got %d", len(value))
|
|
}
|
|
|
|
if !strings.HasSuffix(value, "...") {
|
|
t.Error("Truncated value should end with '...'")
|
|
}
|
|
}
|
|
|
|
func TestTableSorting(t *testing.T) {
|
|
// Test that the table sorting works correctly
|
|
secrets := []*gitea.Secret{
|
|
{Name: "Z_SECRET", Created: time.Now()},
|
|
{Name: "A_SECRET", Created: time.Now()},
|
|
{Name: "M_SECRET", Created: time.Now()},
|
|
}
|
|
|
|
// Test the table sorting logic
|
|
table := table{
|
|
headers: []string{"Name", "Created"},
|
|
}
|
|
|
|
for _, secret := range secrets {
|
|
table.addRow(secret.Name, FormatTime(secret.Created, true))
|
|
}
|
|
|
|
// Sort by first column (Name) in ascending order (false = ascending)
|
|
table.sort(0, false)
|
|
|
|
// Check that the first row is A_SECRET after ascending sorting
|
|
if table.values[0][0] != "A_SECRET" {
|
|
t.Errorf("Expected first sorted value to be 'A_SECRET', got '%s'", table.values[0][0])
|
|
}
|
|
|
|
// Check that the last row is Z_SECRET after ascending sorting
|
|
if table.values[2][0] != "Z_SECRET" {
|
|
t.Errorf("Expected last sorted value to be 'Z_SECRET', got '%s'", table.values[2][0])
|
|
}
|
|
}
|