Merge pull request #1172 from trheyi/main
Add GetAllProviders method and related tests for provider information…
This commit is contained in:
commit
05ecd1798c
10 changed files with 424 additions and 16 deletions
|
|
@ -368,6 +368,18 @@ func (m *Service) GetProvidersByMessageType() map[types.MessageType][]types.Prov
|
|||
return result
|
||||
}
|
||||
|
||||
// GetAllProviders returns all providers
|
||||
func (m *Service) GetAllProviders() []types.Provider {
|
||||
m.mutex.RLock()
|
||||
defer m.mutex.RUnlock()
|
||||
|
||||
providers := make([]types.Provider, 0, len(m.providers))
|
||||
for _, provider := range m.providers {
|
||||
providers = append(providers, provider)
|
||||
}
|
||||
return providers
|
||||
}
|
||||
|
||||
// GetChannels returns all available channels
|
||||
func (m *Service) GetChannels() []string {
|
||||
m.mutex.RLock()
|
||||
|
|
|
|||
|
|
@ -303,6 +303,55 @@ func TestGetProvidersByMessageType(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetAllProviders(t *testing.T) {
|
||||
// Setup test environment variables
|
||||
setupTestEnvironment()
|
||||
|
||||
test.Prepare(t, config.Conf, "YAO_TEST_APPLICATION")
|
||||
defer test.Clean()
|
||||
|
||||
err := Load(config.Conf)
|
||||
require.NoError(t, err, "Load should succeed")
|
||||
|
||||
service, ok := Instance.(*Service)
|
||||
require.True(t, ok, "Instance should be of type *Service")
|
||||
|
||||
// Test getting all providers
|
||||
allProviders := service.GetAllProviders()
|
||||
|
||||
t.Logf("Total providers: %d", len(allProviders))
|
||||
|
||||
// Should have at least some providers
|
||||
assert.GreaterOrEqual(t, len(allProviders), 1, "Should have at least one provider")
|
||||
|
||||
// Verify each provider has required methods
|
||||
for _, provider := range allProviders {
|
||||
assert.NotEmpty(t, provider.GetName(), "Provider should have a name")
|
||||
assert.NotEmpty(t, provider.GetType(), "Provider should have a type")
|
||||
|
||||
// Test GetPublicInfo returns valid data
|
||||
publicInfo := provider.GetPublicInfo()
|
||||
assert.NotEmpty(t, publicInfo.Name, "Public info should have name")
|
||||
assert.NotEmpty(t, publicInfo.Type, "Public info should have type")
|
||||
assert.NotEmpty(t, publicInfo.Description, "Public info should have description")
|
||||
assert.NotNil(t, publicInfo.Capabilities, "Public info should have capabilities")
|
||||
}
|
||||
|
||||
// Verify that all providers are accessible
|
||||
emailProviders := service.GetProviders("email")
|
||||
|
||||
// Note: Some providers may support multiple message types, so this is just a sanity check
|
||||
assert.GreaterOrEqual(t, len(allProviders), len(emailProviders), "Total should be >= email providers")
|
||||
|
||||
// Each provider should be unique by name
|
||||
providerNames := make(map[string]bool)
|
||||
for _, provider := range allProviders {
|
||||
providerName := provider.GetName()
|
||||
assert.False(t, providerNames[providerName], "Provider names should be unique: %s", providerName)
|
||||
providerNames[providerName] = true
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMessage(t *testing.T) {
|
||||
// Setup test environment variables
|
||||
setupTestEnvironment()
|
||||
|
|
|
|||
|
|
@ -209,6 +209,27 @@ func (p *Provider) GetName() string {
|
|||
return p.config.Name
|
||||
}
|
||||
|
||||
// GetPublicInfo returns public information about the provider
|
||||
func (p *Provider) GetPublicInfo() types.ProviderPublicInfo {
|
||||
description := "SMTP email provider"
|
||||
if p.config.Description != "" {
|
||||
description = p.config.Description
|
||||
}
|
||||
|
||||
return types.ProviderPublicInfo{
|
||||
Name: p.config.Name,
|
||||
Type: "mailer",
|
||||
Description: description,
|
||||
Capabilities: []string{"email"},
|
||||
Features: types.Features{
|
||||
SupportsWebhooks: false,
|
||||
SupportsReceiving: p.SupportsReceiving(),
|
||||
SupportsTracking: false,
|
||||
SupportsScheduling: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the provider configuration
|
||||
func (p *Provider) Validate() error {
|
||||
if p.host == "" {
|
||||
|
|
|
|||
|
|
@ -685,3 +685,86 @@ func BenchmarkBuildMessage(b *testing.B) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProvider_GetPublicInfo(t *testing.T) {
|
||||
config := types.ProviderConfig{
|
||||
Name: "test-mailer",
|
||||
Connector: "mailer",
|
||||
Description: "Test SMTP Provider",
|
||||
Options: map[string]interface{}{
|
||||
"smtp": map[string]interface{}{
|
||||
"host": "smtp.example.com",
|
||||
"port": 587,
|
||||
"username": "test@example.com",
|
||||
"password": "testpass",
|
||||
"from": "test@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewMailerProvider(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
info := provider.GetPublicInfo()
|
||||
|
||||
// Verify public information
|
||||
assert.Equal(t, "test-mailer", info.Name)
|
||||
assert.Equal(t, "mailer", info.Type)
|
||||
assert.Equal(t, "Test SMTP Provider", info.Description)
|
||||
assert.Equal(t, false, info.Features.SupportsWebhooks)
|
||||
assert.Equal(t, false, info.Features.SupportsReceiving) // No IMAP config
|
||||
assert.Equal(t, false, info.Features.SupportsTracking)
|
||||
assert.Equal(t, false, info.Features.SupportsScheduling)
|
||||
|
||||
// Verify capabilities
|
||||
assert.Contains(t, info.Capabilities, "email")
|
||||
}
|
||||
|
||||
func TestProvider_GetPublicInfo_DefaultDescription(t *testing.T) {
|
||||
config := types.ProviderConfig{
|
||||
Name: "test-mailer-no-desc",
|
||||
Connector: "mailer",
|
||||
Options: map[string]interface{}{
|
||||
"smtp": map[string]interface{}{
|
||||
"host": "smtp.example.com",
|
||||
"port": 587,
|
||||
"username": "test@example.com",
|
||||
"password": "testpass",
|
||||
"from": "test@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewMailerProvider(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
info := provider.GetPublicInfo()
|
||||
|
||||
// Should use default description when none provided
|
||||
assert.Equal(t, "SMTP email provider", info.Description)
|
||||
}
|
||||
|
||||
func TestProvider_TriggerWebhook(t *testing.T) {
|
||||
config := types.ProviderConfig{
|
||||
Name: "test-mailer",
|
||||
Connector: "mailer",
|
||||
Options: map[string]interface{}{
|
||||
"smtp": map[string]interface{}{
|
||||
"host": "smtp.example.com",
|
||||
"port": 587,
|
||||
"username": "test@example.com",
|
||||
"password": "testpass",
|
||||
"from": "test@example.com",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewMailerProvider(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
// TriggerWebhook should return an error for SMTP providers
|
||||
msg, err := provider.TriggerWebhook(nil)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, msg)
|
||||
assert.Contains(t, err.Error(), "TriggerWebhook not supported for SMTP/mailer provider")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,27 @@ func (p *Provider) GetName() string {
|
|||
return p.config.Name
|
||||
}
|
||||
|
||||
// GetPublicInfo returns public information about the provider
|
||||
func (p *Provider) GetPublicInfo() types.ProviderPublicInfo {
|
||||
description := "Mailgun email service provider"
|
||||
if p.config.Description != "" {
|
||||
description = p.config.Description
|
||||
}
|
||||
|
||||
return types.ProviderPublicInfo{
|
||||
Name: p.config.Name,
|
||||
Type: "mailgun",
|
||||
Description: description,
|
||||
Capabilities: []string{"email", "webhooks", "tracking"},
|
||||
Features: types.Features{
|
||||
SupportsWebhooks: true,
|
||||
SupportsReceiving: false,
|
||||
SupportsTracking: true,
|
||||
SupportsScheduling: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the provider configuration
|
||||
func (p *Provider) Validate() error {
|
||||
if p.domain == "" {
|
||||
|
|
|
|||
|
|
@ -169,3 +169,55 @@ func TestProvider_TriggerWebhook_InvalidInput(t *testing.T) {
|
|||
assert.Nil(t, msg)
|
||||
assert.Contains(t, err.Error(), "expected *gin.Context")
|
||||
}
|
||||
|
||||
func TestProvider_GetPublicInfo(t *testing.T) {
|
||||
config := types.ProviderConfig{
|
||||
Name: "test-mailgun",
|
||||
Connector: "mailgun",
|
||||
Description: "Test Mailgun Provider",
|
||||
Options: map[string]interface{}{
|
||||
"domain": "test.mailgun.org",
|
||||
"api_key": "test-api-key",
|
||||
"from": "test@test.mailgun.org",
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewMailgunProvider(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
info := provider.GetPublicInfo()
|
||||
|
||||
// Verify public information
|
||||
assert.Equal(t, "test-mailgun", info.Name)
|
||||
assert.Equal(t, "mailgun", info.Type)
|
||||
assert.Equal(t, "Test Mailgun Provider", info.Description)
|
||||
assert.Equal(t, true, info.Features.SupportsWebhooks)
|
||||
assert.Equal(t, true, info.Features.SupportsTracking)
|
||||
assert.Equal(t, true, info.Features.SupportsScheduling)
|
||||
assert.Equal(t, false, info.Features.SupportsReceiving)
|
||||
|
||||
// Verify capabilities
|
||||
assert.Contains(t, info.Capabilities, "email")
|
||||
assert.Contains(t, info.Capabilities, "webhooks")
|
||||
assert.Contains(t, info.Capabilities, "tracking")
|
||||
}
|
||||
|
||||
func TestProvider_GetPublicInfo_DefaultDescription(t *testing.T) {
|
||||
config := types.ProviderConfig{
|
||||
Name: "test-mailgun-no-desc",
|
||||
Connector: "mailgun",
|
||||
Options: map[string]interface{}{
|
||||
"domain": "test.mailgun.org",
|
||||
"api_key": "test-api-key",
|
||||
"from": "test@test.mailgun.org",
|
||||
},
|
||||
}
|
||||
|
||||
provider, err := NewMailgunProvider(config)
|
||||
require.NoError(t, err)
|
||||
|
||||
info := provider.GetPublicInfo()
|
||||
|
||||
// Should use default description when none provided
|
||||
assert.Equal(t, "Mailgun email service provider", info.Description)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,6 +128,41 @@ func (p *Provider) GetName() string {
|
|||
return p.config.Name
|
||||
}
|
||||
|
||||
// GetPublicInfo returns public information about the provider
|
||||
func (p *Provider) GetPublicInfo() types.ProviderPublicInfo {
|
||||
description := "Twilio multi-channel communication provider"
|
||||
if p.config.Description != "" {
|
||||
description = p.config.Description
|
||||
}
|
||||
|
||||
capabilities := []string{}
|
||||
if p.fromPhone != "" || p.messagingServiceSID != "" {
|
||||
capabilities = append(capabilities, "sms")
|
||||
}
|
||||
if p.fromPhone != "" {
|
||||
capabilities = append(capabilities, "whatsapp")
|
||||
}
|
||||
if p.sendGridAPIKey != "" {
|
||||
capabilities = append(capabilities, "email")
|
||||
}
|
||||
if len(capabilities) == 0 {
|
||||
capabilities = []string{"sms", "whatsapp", "email"} // Default capabilities
|
||||
}
|
||||
|
||||
return types.ProviderPublicInfo{
|
||||
Name: p.config.Name,
|
||||
Type: "twilio",
|
||||
Description: description,
|
||||
Capabilities: capabilities,
|
||||
Features: types.Features{
|
||||
SupportsWebhooks: true,
|
||||
SupportsReceiving: false,
|
||||
SupportsTracking: true,
|
||||
SupportsScheduling: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate validates the provider configuration
|
||||
func (p *Provider) Validate() error {
|
||||
if p.accountSID == "" {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ type Provider interface {
|
|||
// GetName returns the provider name/identifier
|
||||
GetName() string
|
||||
|
||||
// GetPublicInfo returns public information about the provider (name, description, type)
|
||||
GetPublicInfo() ProviderPublicInfo
|
||||
|
||||
// Validate validates the provider configuration
|
||||
Validate() error
|
||||
|
||||
|
|
@ -46,6 +49,9 @@ type Messenger interface {
|
|||
// GetProviders returns all providers for a channel type
|
||||
GetProviders(channelType string) []Provider
|
||||
|
||||
// GetAllProviders returns all providers
|
||||
GetAllProviders() []Provider
|
||||
|
||||
// GetChannels returns all available channels
|
||||
GetChannels() []string
|
||||
|
||||
|
|
|
|||
|
|
@ -117,3 +117,20 @@ type SendResult struct {
|
|||
SentAt time.Time `json:"sent_at"`
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
// ProviderPublicInfo defines the public information structure for providers
|
||||
type ProviderPublicInfo struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Description string `json:"description"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
Features Features `json:"features"`
|
||||
}
|
||||
|
||||
// Features defines the features supported by a provider
|
||||
type Features struct {
|
||||
SupportsWebhooks bool `json:"supports_webhooks"`
|
||||
SupportsReceiving bool `json:"supports_receiving"`
|
||||
SupportsTracking bool `json:"supports_tracking"`
|
||||
SupportsScheduling bool `json:"supports_scheduling"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,25 @@
|
|||
package messenger
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/yaoapp/kun/log"
|
||||
"github.com/yaoapp/yao/messenger"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/messenger/types"
|
||||
oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
|
||||
"github.com/yaoapp/yao/openapi/response"
|
||||
)
|
||||
|
||||
// Attach attaches the messenger webhook handlers to the router
|
||||
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
func Attach(group *gin.RouterGroup, oauth oauthTypes.OAuth) {
|
||||
|
||||
// Webhook endpoint with provider parameter - public interface
|
||||
group.GET("/webhook/:provider", webhookHandler)
|
||||
group.POST("/webhook/:provider", webhookHandler)
|
||||
|
||||
// Private API endpoints for provider and channel information
|
||||
group.GET("/providers", oauth.Guard, getProvidersHandler)
|
||||
group.GET("/providers/:name", oauth.Guard, getProviderHandler)
|
||||
group.GET("/channels", oauth.Guard, getChannelsHandler)
|
||||
}
|
||||
|
||||
// webhookHandler is the handler for webhook endpoint
|
||||
|
|
@ -22,18 +27,22 @@ func webhookHandler(c *gin.Context) {
|
|||
// Get provider name from URL parameter
|
||||
providerName := c.Param("provider")
|
||||
if providerName == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "provider parameter is required",
|
||||
})
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidRequest.Code,
|
||||
ErrorDescription: "Provider parameter is required",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusBadRequest, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if messenger service is available
|
||||
if messenger.Instance == nil {
|
||||
log.Warn("[OpenAPI Messenger] Messenger service not initialized")
|
||||
c.JSON(http.StatusServiceUnavailable, gin.H{
|
||||
"error": "messenger service not available",
|
||||
})
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrTemporarilyUnavailable.Code,
|
||||
ErrorDescription: "Messenger service not available",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusServiceUnavailable, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -41,17 +50,120 @@ func webhookHandler(c *gin.Context) {
|
|||
err := messenger.Instance.TriggerWebhook(providerName, c)
|
||||
if err != nil {
|
||||
log.Error("[OpenAPI Messenger] Failed to process webhook for provider %s: %v", providerName, err)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{
|
||||
"error": "failed to process webhook",
|
||||
"details": err.Error(),
|
||||
})
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrServerError.Code,
|
||||
ErrorDescription: "Failed to process webhook: " + err.Error(),
|
||||
}
|
||||
response.RespondWithError(c, response.StatusInternalServerError, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Return success response
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
successResp := gin.H{
|
||||
"status": "received",
|
||||
"message": "webhook processed successfully",
|
||||
"provider": providerName,
|
||||
})
|
||||
}
|
||||
response.RespondWithSuccess(c, response.StatusOK, successResp)
|
||||
}
|
||||
|
||||
// getProviderHandler returns public information about a specific provider
|
||||
func getProviderHandler(c *gin.Context) {
|
||||
providerName := c.Param("name")
|
||||
if providerName == "" {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrInvalidRequest.Code,
|
||||
ErrorDescription: "Provider name is required",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusBadRequest, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if messenger service is available
|
||||
if messenger.Instance == nil {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrTemporarilyUnavailable.Code,
|
||||
ErrorDescription: "Messenger service not available",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusServiceUnavailable, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get provider
|
||||
provider, err := messenger.Instance.GetProvider(providerName)
|
||||
if err != nil {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: "provider_not_found",
|
||||
ErrorDescription: "Provider not found: " + providerName,
|
||||
}
|
||||
response.RespondWithError(c, response.StatusNotFound, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Return public information directly
|
||||
response.RespondWithSuccess(c, response.StatusOK, provider.GetPublicInfo())
|
||||
}
|
||||
|
||||
// getProvidersHandler returns public information about all providers, with optional channel type filter
|
||||
func getProvidersHandler(c *gin.Context) {
|
||||
// Check if messenger service is available
|
||||
if messenger.Instance == nil {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrTemporarilyUnavailable.Code,
|
||||
ErrorDescription: "Messenger service not available",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusServiceUnavailable, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get optional channel type filter from query parameter
|
||||
channelType := c.Query("channel_type")
|
||||
|
||||
var providers []types.Provider
|
||||
if channelType != "" {
|
||||
// Filter by channel type
|
||||
providers = messenger.Instance.GetProviders(channelType)
|
||||
} else {
|
||||
// Get all providers
|
||||
providers = messenger.Instance.GetAllProviders()
|
||||
}
|
||||
|
||||
// Convert to public information
|
||||
publicProviders := make([]interface{}, 0, len(providers))
|
||||
for _, provider := range providers {
|
||||
publicProviders = append(publicProviders, provider.GetPublicInfo())
|
||||
}
|
||||
|
||||
successResp := gin.H{
|
||||
"providers": publicProviders,
|
||||
"count": len(publicProviders),
|
||||
}
|
||||
|
||||
if channelType != "" {
|
||||
successResp["channel_type"] = channelType
|
||||
}
|
||||
|
||||
response.RespondWithSuccess(c, response.StatusOK, successResp)
|
||||
}
|
||||
|
||||
// getChannelsHandler returns all available channels
|
||||
func getChannelsHandler(c *gin.Context) {
|
||||
// Check if messenger service is available
|
||||
if messenger.Instance == nil {
|
||||
errorResp := &response.ErrorResponse{
|
||||
Code: response.ErrTemporarilyUnavailable.Code,
|
||||
ErrorDescription: "Messenger service not available",
|
||||
}
|
||||
response.RespondWithError(c, response.StatusServiceUnavailable, errorResp)
|
||||
return
|
||||
}
|
||||
|
||||
// Get all channels
|
||||
channels := messenger.Instance.GetChannels()
|
||||
|
||||
successResp := gin.H{
|
||||
"channels": channels,
|
||||
"count": len(channels),
|
||||
}
|
||||
response.RespondWithSuccess(c, response.StatusOK, successResp)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue