mirror of
				https://gitea.com/gitea/tea.git
				synced 2025-10-31 09:15:26 +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
	 Ross Golder
					Ross Golder