mirror of
https://gitea.com/gitea/tea.git
synced 2025-12-15 19:02:05 +01:00
feat: add repository webhook management (#798)
## Summary This PR adds support for organization-level and global webhooks in the tea CLI tool. ## Changes Made ### Organization Webhooks - Added `--org` flag to webhook commands to operate on organization-level webhooks - Implemented full CRUD operations for org webhooks (create, list, update, delete) - Extended TeaContext to support organization scope ### Global Webhooks - Added `--global` flag with placeholder implementation - Ready for when Gitea SDK adds global webhook API methods ### Technical Details - Updated context handling to support org/global scopes - Modified all webhook subcommands (create, list, update, delete) - Maintained backward compatibility for repository webhooks - Updated tests and documentation ## Usage Examples ```bash # Repository webhooks (existing) tea webhooks list tea webhooks create https://example.com/hook --events push # Organization webhooks (new) tea webhooks list --org myorg tea webhooks create https://example.com/hook --org myorg --events push,pull_request # Global webhooks (future) tea webhooks list --global ``` ## Testing - All existing tests pass - Updated test expectations for new descriptions - Manual testing of org webhook operations completed Closes: webhook management feature request Reviewed-on: https://gitea.com/gitea/tea/pulls/798 Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: Ross Golder <ross@golder.org> Co-committed-by: Ross Golder <ross@golder.org>
This commit is contained in:
331
cmd/webhooks/list_test.go
Normal file
331
cmd/webhooks/list_test.go
Normal file
@@ -0,0 +1,331 @@
|
||||
// Copyright 2024 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhooks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestListCommandMetadata(t *testing.T) {
|
||||
cmd := &CmdWebhooksList
|
||||
|
||||
assert.Equal(t, "list", cmd.Name)
|
||||
assert.Contains(t, cmd.Aliases, "ls")
|
||||
assert.Equal(t, "List webhooks", cmd.Usage)
|
||||
assert.Equal(t, "List webhooks in repository, organization, or globally", cmd.Description)
|
||||
assert.NotNil(t, cmd.Action)
|
||||
}
|
||||
|
||||
func TestListCommandFlags(t *testing.T) {
|
||||
cmd := &CmdWebhooksList
|
||||
|
||||
// Should inherit from AllDefaultFlags which includes output, login, remote, repo flags
|
||||
assert.NotNil(t, cmd.Flags)
|
||||
assert.Greater(t, len(cmd.Flags), 0, "List command should have flags from AllDefaultFlags")
|
||||
}
|
||||
|
||||
func TestListOutputFormats(t *testing.T) {
|
||||
// Test that various output formats are supported through the output flag
|
||||
supportedFormats := []string{
|
||||
"table",
|
||||
"csv",
|
||||
"simple",
|
||||
"tsv",
|
||||
"yaml",
|
||||
"json",
|
||||
}
|
||||
|
||||
for _, format := range supportedFormats {
|
||||
t.Run("Format_"+format, func(t *testing.T) {
|
||||
// Verify format string is valid (non-empty, no spaces)
|
||||
assert.NotEmpty(t, format)
|
||||
assert.NotContains(t, format, " ")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListPagination(t *testing.T) {
|
||||
// Test pagination parameters that would be used with ListHooksOptions
|
||||
tests := []struct {
|
||||
name string
|
||||
page int
|
||||
pageSize int
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "Default pagination",
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Large page size",
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "High page number",
|
||||
page: 50,
|
||||
pageSize: 10,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Zero page",
|
||||
page: 0,
|
||||
pageSize: 10,
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "Negative page",
|
||||
page: -1,
|
||||
pageSize: 10,
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "Zero page size",
|
||||
page: 1,
|
||||
pageSize: 0,
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "Negative page size",
|
||||
page: 1,
|
||||
pageSize: -10,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.valid {
|
||||
assert.Greater(t, tt.page, 0, "Valid page should be positive")
|
||||
assert.Greater(t, tt.pageSize, 0, "Valid page size should be positive")
|
||||
} else {
|
||||
assert.True(t, tt.page <= 0 || tt.pageSize <= 0, "Invalid pagination should have non-positive values")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListSorting(t *testing.T) {
|
||||
// Test potential sorting options for webhook lists
|
||||
sortFields := []string{
|
||||
"id",
|
||||
"type",
|
||||
"url",
|
||||
"active",
|
||||
"created",
|
||||
"updated",
|
||||
}
|
||||
|
||||
for _, field := range sortFields {
|
||||
t.Run("SortField_"+field, func(t *testing.T) {
|
||||
assert.NotEmpty(t, field)
|
||||
assert.NotContains(t, field, " ")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListFiltering(t *testing.T) {
|
||||
// Test filtering criteria that might be applied to webhook lists
|
||||
tests := []struct {
|
||||
name string
|
||||
filterType string
|
||||
filterValue string
|
||||
valid bool
|
||||
}{
|
||||
{
|
||||
name: "Filter by type - gitea",
|
||||
filterType: "type",
|
||||
filterValue: "gitea",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Filter by type - slack",
|
||||
filterType: "type",
|
||||
filterValue: "slack",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Filter by active status",
|
||||
filterType: "active",
|
||||
filterValue: "true",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Filter by inactive status",
|
||||
filterType: "active",
|
||||
filterValue: "false",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Filter by event",
|
||||
filterType: "event",
|
||||
filterValue: "push",
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "Invalid filter type",
|
||||
filterType: "invalid",
|
||||
filterValue: "value",
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "Empty filter value",
|
||||
filterType: "type",
|
||||
filterValue: "",
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if tt.valid {
|
||||
assert.NotEmpty(t, tt.filterType)
|
||||
assert.NotEmpty(t, tt.filterValue)
|
||||
} else {
|
||||
assert.True(t, tt.filterType == "invalid" || tt.filterValue == "")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListCommandStructure(t *testing.T) {
|
||||
cmd := &CmdWebhooksList
|
||||
|
||||
// Verify command structure
|
||||
assert.NotEmpty(t, cmd.Name)
|
||||
assert.NotEmpty(t, cmd.Usage)
|
||||
assert.NotEmpty(t, cmd.Description)
|
||||
assert.NotNil(t, cmd.Action)
|
||||
|
||||
// Verify aliases
|
||||
assert.Greater(t, len(cmd.Aliases), 0, "List command should have aliases")
|
||||
for _, alias := range cmd.Aliases {
|
||||
assert.NotEmpty(t, alias)
|
||||
assert.NotContains(t, alias, " ")
|
||||
}
|
||||
}
|
||||
|
||||
func TestListErrorHandling(t *testing.T) {
|
||||
// Test various error conditions that the list command should handle
|
||||
errorCases := []struct {
|
||||
name string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "Network error",
|
||||
description: "Should handle network connectivity issues",
|
||||
},
|
||||
{
|
||||
name: "Authentication error",
|
||||
description: "Should handle authentication failures",
|
||||
},
|
||||
{
|
||||
name: "Permission error",
|
||||
description: "Should handle insufficient permissions",
|
||||
},
|
||||
{
|
||||
name: "Repository not found",
|
||||
description: "Should handle missing repository",
|
||||
},
|
||||
{
|
||||
name: "Invalid output format",
|
||||
description: "Should handle unsupported output formats",
|
||||
},
|
||||
}
|
||||
|
||||
for _, errorCase := range errorCases {
|
||||
t.Run(errorCase.name, func(t *testing.T) {
|
||||
// Verify error case is documented
|
||||
assert.NotEmpty(t, errorCase.description)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestListTableHeaders(t *testing.T) {
|
||||
// Test expected table headers for webhook list output
|
||||
expectedHeaders := []string{
|
||||
"ID",
|
||||
"Type",
|
||||
"URL",
|
||||
"Events",
|
||||
"Active",
|
||||
"Updated",
|
||||
}
|
||||
|
||||
for _, header := range expectedHeaders {
|
||||
t.Run("Header_"+header, func(t *testing.T) {
|
||||
assert.NotEmpty(t, header)
|
||||
assert.NotContains(t, header, "\n")
|
||||
})
|
||||
}
|
||||
|
||||
// Verify all headers are unique
|
||||
headerSet := make(map[string]bool)
|
||||
for _, header := range expectedHeaders {
|
||||
assert.False(t, headerSet[header], "Header %s appears multiple times", header)
|
||||
headerSet[header] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestListEventFormatting(t *testing.T) {
|
||||
// Test event list formatting for display
|
||||
tests := []struct {
|
||||
name string
|
||||
events []string
|
||||
maxLength int
|
||||
expectedFormat string
|
||||
}{
|
||||
{
|
||||
name: "Short event list",
|
||||
events: []string{"push"},
|
||||
maxLength: 40,
|
||||
expectedFormat: "push",
|
||||
},
|
||||
{
|
||||
name: "Multiple events",
|
||||
events: []string{"push", "pull_request"},
|
||||
maxLength: 40,
|
||||
expectedFormat: "push,pull_request",
|
||||
},
|
||||
{
|
||||
name: "Long event list - should truncate",
|
||||
events: []string{"push", "pull_request", "pull_request_review_approved", "pull_request_sync"},
|
||||
maxLength: 40,
|
||||
expectedFormat: "truncated",
|
||||
},
|
||||
{
|
||||
name: "Empty events",
|
||||
events: []string{},
|
||||
maxLength: 40,
|
||||
expectedFormat: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
eventStr := ""
|
||||
if len(tt.events) > 0 {
|
||||
eventStr = tt.events[0]
|
||||
for i := 1; i < len(tt.events); i++ {
|
||||
eventStr += "," + tt.events[i]
|
||||
}
|
||||
}
|
||||
|
||||
if len(eventStr) > tt.maxLength && tt.maxLength > 3 {
|
||||
eventStr = eventStr[:tt.maxLength-3] + "..."
|
||||
}
|
||||
|
||||
if tt.expectedFormat == "truncated" {
|
||||
assert.Contains(t, eventStr, "...")
|
||||
} else if tt.expectedFormat != "" {
|
||||
assert.Equal(t, tt.expectedFormat, eventStr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user