mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-30 16:55:25 +01:00 
			
		
		
		
	 3495ec5ed4
			
		
	
	3495ec5ed4
	
	
	
		
			
			## 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>
		
			
				
	
	
		
			332 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			332 lines
		
	
	
		
			7.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // 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)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |