fix: queries for webhooks + improve tests

This commit is contained in:
Lakhan Samani 2023-03-29 07:06:33 +05:30
parent a38e9d4e6c
commit e822b6f31a
45 changed files with 213 additions and 138 deletions

View File

@ -179,6 +179,7 @@ export const envSubViews = {
export enum WebhookInputDataFields { export enum WebhookInputDataFields {
ID = 'id', ID = 'id',
EVENT_DESCRIPTION = 'event_description',
EVENT_NAME = 'event_name', EVENT_NAME = 'event_name',
ENDPOINT = 'endpoint', ENDPOINT = 'endpoint',
ENABLED = 'enabled', ENABLED = 'enabled',

View File

@ -118,6 +118,8 @@ export const WebhooksDataQuery = `
_webhooks(params: $params){ _webhooks(params: $params){
webhooks{ webhooks{
id id
event
event_description
event_name event_name
endpoint endpoint
enabled enabled

View File

@ -56,6 +56,7 @@ interface paginationPropTypes {
interface webhookDataTypes { interface webhookDataTypes {
[WebhookInputDataFields.ID]: string; [WebhookInputDataFields.ID]: string;
[WebhookInputDataFields.EVENT_NAME]: string; [WebhookInputDataFields.EVENT_NAME]: string;
[WebhookInputDataFields.EVENT_DESCRIPTION]: string;
[WebhookInputDataFields.ENDPOINT]: string; [WebhookInputDataFields.ENDPOINT]: string;
[WebhookInputDataFields.ENABLED]: boolean; [WebhookInputDataFields.ENABLED]: boolean;
[WebhookInputDataFields.HEADERS]?: Record<string, string>; [WebhookInputDataFields.HEADERS]?: Record<string, string>;
@ -134,6 +135,7 @@ const Webhooks = () => {
<Thead> <Thead>
<Tr> <Tr>
<Th>Event Name</Th> <Th>Event Name</Th>
<Th>Event Description</Th>
<Th>Endpoint</Th> <Th>Endpoint</Th>
<Th>Enabled</Th> <Th>Enabled</Th>
<Th>Headers</Th> <Th>Headers</Th>
@ -147,7 +149,10 @@ const Webhooks = () => {
style={{ fontSize: 14 }} style={{ fontSize: 14 }}
> >
<Td maxW="300"> <Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_NAME]} {webhook[WebhookInputDataFields.EVENT_NAME].split('-')[0]}
</Td>
<Td maxW="300">
{webhook[WebhookInputDataFields.EVENT_DESCRIPTION]}
</Td> </Td>
<Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td> <Td>{webhook[WebhookInputDataFields.ENDPOINT]}</Td>
<Td> <Td>
@ -264,7 +269,7 @@ const Webhooks = () => {
</Text> </Text>
</Text> </Text>
<Flex alignItems="center"> <Flex alignItems="center">
<Text flexShrink="0">Go to page:</Text>{' '} <Text>Go to page:</Text>{' '}
<NumberInput <NumberInput
ml={2} ml={2}
mr={8} mr={8}

View File

@ -34,18 +34,12 @@ func (w *Webhook) AsAPIWebhook() *model.Webhook {
if strings.Contains(id, Collections.Webhook+"/") { if strings.Contains(id, Collections.Webhook+"/") {
id = strings.TrimPrefix(id, Collections.Webhook+"/") id = strings.TrimPrefix(id, Collections.Webhook+"/")
} }
// If event name contains timestamp trim that part
if strings.Contains(w.EventName, "-") {
splitData := strings.Split(w.EventName, "-")
w.EventName = splitData[0]
}
// set default title to event name without dot(.) // set default title to event name without dot(.)
if w.EventDescription == "" { if w.EventDescription == "" {
w.EventDescription = strings.Join(strings.Split(w.EventName, "."), " ") w.EventDescription = strings.Join(strings.Split(w.EventName, "."), " ")
} }
return &model.Webhook{ return &model.Webhook{
ID: id, ID: id,
// Title: refs.NewStringRef(w.EventDescription),
EventName: refs.NewStringRef(w.EventName), EventName: refs.NewStringRef(w.EventName),
Endpoint: refs.NewStringRef(w.EndPoint), Endpoint: refs.NewStringRef(w.EndPoint),
Headers: headersMap, Headers: headersMap,

View File

@ -20,9 +20,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
} }
webhook.Key = webhook.ID webhook.Key = webhook.ID
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()

View File

@ -207,6 +207,13 @@ func NewProvider() (*provider, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// add event_description to webhook table
webhookAlterQuery := fmt.Sprintf(`ALTER TABLE %s.%s ADD (event_description text);`, KeySpace, models.Collections.Webhook)
err = session.Query(webhookAlterQuery).Exec()
if err != nil {
log.Debug("Failed to alter table as column exists: ", err)
// continue
}
webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog) webhookLogCollectionQuery := fmt.Sprintf("CREATE TABLE IF NOT EXISTS %s.%s (id text, http_status bigint, response text, request text, webhook_id text,updated_at bigint, created_at bigint, PRIMARY KEY (id))", KeySpace, models.Collections.WebhookLog)
err = session.Query(webhookLogCollectionQuery).Exec() err = session.Query(webhookLogCollectionQuery).Exec()

View File

@ -22,9 +22,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventDescription, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt) insertQuery := fmt.Sprintf("INSERT INTO %s (id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at) VALUES ('%s', '%s', '%s', '%s', '%s', %t, %d, %d)", KeySpace+"."+models.Collections.Webhook, webhook.ID, webhook.EventDescription, webhook.EventName, webhook.EndPoint, webhook.Headers, webhook.Enabled, webhook.CreatedAt, webhook.UpdatedAt)
@ -129,7 +126,7 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
query := fmt.Sprintf(`SELECT id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name LIKE '%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName+"%s") query := fmt.Sprintf(`SELECT id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s WHERE event_name LIKE '%s' ALLOW FILTERING`, KeySpace+"."+models.Collections.Webhook, eventName+"%")
scanner := p.db.Query(query).Iter().Scanner() scanner := p.db.Query(query).Iter().Scanner()
webhooks := []*model.Webhook{} webhooks := []*model.Webhook{}
for scanner.Next() { for scanner.Next() {

View File

@ -22,9 +22,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
insertOpt := gocb.InsertOptions{ insertOpt := gocb.InsertOptions{
@ -90,8 +87,6 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} else if err := queryResult.Err(); err != nil {
return nil, err
} }
for queryResult.Next() { for queryResult.Next() {
var webhook models.Webhook var webhook models.Webhook
@ -101,6 +96,9 @@ func (p *provider) ListWebhook(ctx context.Context, pagination model.Pagination)
} }
webhooks = append(webhooks, webhook.AsAPIWebhook()) webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
if err := queryResult.Err(); err != nil {
return nil, err
}
return &model.Webhooks{ return &model.Webhooks{
Pagination: &paginationClone, Pagination: &paginationClone,
Webhooks: webhooks, Webhooks: webhooks,
@ -131,8 +129,8 @@ func (p *provider) GetWebhookByID(ctx context.Context, webhookID string) (*model
// GetWebhookByEventName to get webhook by event_name // GetWebhookByEventName to get webhook by event_name
func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) { func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string) ([]*model.Webhook, error) {
params := make(map[string]interface{}, 1) params := make(map[string]interface{}, 1)
params["event_name"] = eventName + "%" // params["event_name"] = eventName + "%"
query := fmt.Sprintf(`SELECT _id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name LIKE $event_name`, p.scopeName, models.Collections.Webhook) query := fmt.Sprintf(`SELECT _id, event_description, event_name, endpoint, headers, enabled, created_at, updated_at FROM %s.%s WHERE event_name LIKE '%s'`, p.scopeName, models.Collections.Webhook, eventName+"%")
queryResult, err := p.db.Query(query, &gocb.QueryOptions{ queryResult, err := p.db.Query(query, &gocb.QueryOptions{
Context: ctx, Context: ctx,
ScanConsistency: gocb.QueryScanConsistencyRequestPlus, ScanConsistency: gocb.QueryScanConsistencyRequestPlus,
@ -140,8 +138,6 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} else if err := queryResult.Err(); err != nil {
return nil, err
} }
webhooks := []*model.Webhook{} webhooks := []*model.Webhook{}
for queryResult.Next() { for queryResult.Next() {
@ -152,12 +148,14 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
} }
webhooks = append(webhooks, webhook.AsAPIWebhook()) webhooks = append(webhooks, webhook.AsAPIWebhook())
} }
if err := queryResult.Err(); err != nil {
return nil, err
}
return webhooks, nil return webhooks, nil
} }
// DeleteWebhook to delete webhook // DeleteWebhook to delete webhook
func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error { func (p *provider) DeleteWebhook(ctx context.Context, webhook *model.Webhook) error {
params := make(map[string]interface{}, 1) params := make(map[string]interface{}, 1)
params["webhook_id"] = webhook.ID params["webhook_id"] = webhook.ID
removeOpt := gocb.RemoveOptions{ removeOpt := gocb.RemoveOptions{

View File

@ -23,9 +23,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
err := collection.Put(webhook).RunWithContext(ctx) err := collection.Put(webhook).RunWithContext(ctx)

View File

@ -21,9 +21,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
@ -99,9 +96,9 @@ func (p *provider) GetWebhookByEventName(ctx context.Context, eventName string)
webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection()) webhookCollection := p.db.Collection(models.Collections.Webhook, options.Collection())
opts := options.Find() opts := options.Find()
opts.SetSort(bson.M{"created_at": -1}) opts.SetSort(bson.M{"created_at": -1})
cursor, err := webhookCollection.Find(ctx, bson.M{ cursor, err := webhookCollection.Find(ctx, bson.M{"event_name": bson.M{
"event_name": fmt.Sprintf("'^%s'", eventName), "$regex": fmt.Sprintf("^%s", eventName),
}, opts) }}, opts)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -19,9 +19,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
return webhook.AsAPIWebhook(), nil return webhook.AsAPIWebhook(), nil

View File

@ -19,9 +19,6 @@ func (p *provider) AddWebhook(ctx context.Context, webhook models.Webhook) (*mod
webhook.Key = webhook.ID webhook.Key = webhook.ID
webhook.CreatedAt = time.Now().Unix() webhook.CreatedAt = time.Now().Unix()
webhook.UpdatedAt = time.Now().Unix() webhook.UpdatedAt = time.Now().Unix()
if webhook.EventDescription == "" {
webhook.EventDescription = strings.Join(strings.Split(webhook.EventName, "."), " ")
}
// Add timestamp to make event name unique for legacy version // Add timestamp to make event name unique for legacy version
webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix()) webhook.EventName = fmt.Sprintf("%s-%d", webhook.EventName, time.Now().Unix())
res := p.db.Create(&webhook) res := p.db.Create(&webhook)

View File

@ -2218,7 +2218,7 @@ type GenerateJWTKeysResponse {
type Webhook { type Webhook {
id: ID! id: ID!
event_name: String event_name: String # this is unique string
event_description: String event_description: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean

View File

@ -168,7 +168,7 @@ type GenerateJWTKeysResponse {
type Webhook { type Webhook {
id: ID! id: ID!
event_name: String event_name: String # this is unique string
event_description: String event_description: String
endpoint: String endpoint: String
enabled: Boolean enabled: Boolean

View File

@ -23,27 +23,26 @@ func AddWebhookResolver(ctx context.Context, params model.AddWebhookRequest) (*m
log.Debug("Failed to get GinContext: ", err) log.Debug("Failed to get GinContext: ", err)
return nil, err return nil, err
} }
if !token.IsSuperAdmin(gc) { if !token.IsSuperAdmin(gc) {
log.Debug("Not logged in as super admin") log.Debug("Not logged in as super admin")
return nil, fmt.Errorf("unauthorized") return nil, fmt.Errorf("unauthorized")
} }
if !validators.IsValidWebhookEventName(params.EventName) { if !validators.IsValidWebhookEventName(params.EventName) {
log.Debug("Invalid Event Name: ", params.EventName) log.Debug("Invalid Event Name: ", params.EventName)
return nil, fmt.Errorf("invalid event name %s", params.EventName) return nil, fmt.Errorf("invalid event name %s", params.EventName)
} }
if strings.TrimSpace(params.Endpoint) == "" { if strings.TrimSpace(params.Endpoint) == "" {
log.Debug("empty endpoint not allowed") log.Debug("empty endpoint not allowed")
return nil, fmt.Errorf("empty endpoint not allowed") return nil, fmt.Errorf("empty endpoint not allowed")
} }
headerBytes, err := json.Marshal(params.Headers) headerBytes, err := json.Marshal(params.Headers)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if params.EventDescription == nil {
params.EventDescription = refs.NewStringRef(strings.Join(strings.Split(params.EventName, "."), " "))
}
_, err = db.Provider.AddWebhook(ctx, models.Webhook{ _, err = db.Provider.AddWebhook(ctx, models.Webhook{
EventDescription: refs.StringValue(params.EventDescription), EventDescription: refs.StringValue(params.EventDescription),
EventName: params.EventName, EventName: params.EventName,

View File

@ -28,13 +28,11 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
log.Debug("Not logged in as super admin") log.Debug("Not logged in as super admin")
return nil, fmt.Errorf("unauthorized") return nil, fmt.Errorf("unauthorized")
} }
webhook, err := db.Provider.GetWebhookByID(ctx, params.ID) webhook, err := db.Provider.GetWebhookByID(ctx, params.ID)
if err != nil { if err != nil {
log.Debug("failed to get webhook: ", err) log.Debug("failed to get webhook: ", err)
return nil, err return nil, err
} }
headersString := "" headersString := ""
if webhook.Headers != nil { if webhook.Headers != nil {
headerBytes, err := json.Marshal(webhook.Headers) headerBytes, err := json.Marshal(webhook.Headers)
@ -43,17 +41,16 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
} }
headersString = string(headerBytes) headersString = string(headerBytes)
} }
webhookDetails := models.Webhook{ webhookDetails := models.Webhook{
ID: webhook.ID, ID: webhook.ID,
Key: webhook.ID, Key: webhook.ID,
EventName: refs.StringValue(webhook.EventName), EventName: refs.StringValue(webhook.EventName),
EventDescription: refs.StringValue(webhook.EventDescription),
EndPoint: refs.StringValue(webhook.Endpoint), EndPoint: refs.StringValue(webhook.Endpoint),
Enabled: refs.BoolValue(webhook.Enabled), Enabled: refs.BoolValue(webhook.Enabled),
Headers: headersString, Headers: headersString,
CreatedAt: refs.Int64Value(webhook.CreatedAt), CreatedAt: refs.Int64Value(webhook.CreatedAt),
} }
if params.EventName != nil && webhookDetails.EventName != refs.StringValue(params.EventName) { if params.EventName != nil && webhookDetails.EventName != refs.StringValue(params.EventName) {
if isValid := validators.IsValidWebhookEventName(refs.StringValue(params.EventName)); !isValid { if isValid := validators.IsValidWebhookEventName(refs.StringValue(params.EventName)); !isValid {
log.Debug("invalid event name: ", refs.StringValue(params.EventName)) log.Debug("invalid event name: ", refs.StringValue(params.EventName))
@ -61,7 +58,6 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
} }
webhookDetails.EventName = refs.StringValue(params.EventName) webhookDetails.EventName = refs.StringValue(params.EventName)
} }
if params.Endpoint != nil && webhookDetails.EndPoint != refs.StringValue(params.Endpoint) { if params.Endpoint != nil && webhookDetails.EndPoint != refs.StringValue(params.Endpoint) {
if strings.TrimSpace(refs.StringValue(params.Endpoint)) == "" { if strings.TrimSpace(refs.StringValue(params.Endpoint)) == "" {
log.Debug("empty endpoint not allowed") log.Debug("empty endpoint not allowed")
@ -69,11 +65,12 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
} }
webhookDetails.EndPoint = refs.StringValue(params.Endpoint) webhookDetails.EndPoint = refs.StringValue(params.Endpoint)
} }
if params.Enabled != nil && webhookDetails.Enabled != refs.BoolValue(params.Enabled) { if params.Enabled != nil && webhookDetails.Enabled != refs.BoolValue(params.Enabled) {
webhookDetails.Enabled = refs.BoolValue(params.Enabled) webhookDetails.Enabled = refs.BoolValue(params.Enabled)
} }
if params.EventDescription != nil && webhookDetails.EventDescription != refs.StringValue(params.EventDescription) {
webhookDetails.EventDescription = refs.StringValue(params.EventDescription)
}
if params.Headers != nil { if params.Headers != nil {
headerBytes, err := json.Marshal(params.Headers) headerBytes, err := json.Marshal(params.Headers)
if err != nil { if err != nil {
@ -83,12 +80,10 @@ func UpdateWebhookResolver(ctx context.Context, params model.UpdateWebhookReques
webhookDetails.Headers = string(headerBytes) webhookDetails.Headers = string(headerBytes)
} }
_, err = db.Provider.UpdateWebhook(ctx, webhookDetails) _, err = db.Provider.UpdateWebhook(ctx, webhookDetails)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &model.Response{ return &model.Response{
Message: `Webhook updated successfully.`, Message: `Webhook updated successfully.`,
}, nil }, nil

View File

@ -3,11 +3,13 @@ package test
import ( import (
"fmt" "fmt"
"testing" "testing"
"time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -21,7 +23,6 @@ func addWebhookTest(t *testing.T, s TestSetup) {
h, err := crypto.EncryptPassword(adminSecret) h, err := crypto.EncryptPassword(adminSecret)
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
for _, eventType := range s.TestInfo.TestWebhookEventTypes { for _, eventType := range s.TestInfo.TestWebhookEventTypes {
webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{ webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
EventName: eventType, EventName: eventType,
@ -35,5 +36,21 @@ func addWebhookTest(t *testing.T, s TestSetup) {
assert.NotNil(t, webhook) assert.NotNil(t, webhook)
assert.NotEmpty(t, webhook.Message) assert.NotEmpty(t, webhook.Message)
} }
time.Sleep(2 * time.Second)
// Allow setting multiple webhooks for same event
for _, eventType := range s.TestInfo.TestWebhookEventTypes {
webhook, err := resolvers.AddWebhookResolver(ctx, model.AddWebhookRequest{
EventName: eventType,
Endpoint: s.TestInfo.WebhookEndpoint,
Enabled: true,
EventDescription: refs.NewStringRef(eventType + "-2"),
Headers: map[string]interface{}{
"x-test": "foo",
},
})
assert.NoError(t, err)
assert.NotNil(t, webhook)
assert.NotEmpty(t, webhook.Message)
}
}) })
} }

View File

@ -25,6 +25,6 @@ func adminSignupTests(t *testing.T, s TestSetup) {
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin123", AdminSecret: "admin123",
}) })
assert.Nil(t, err) assert.NoError(t, err)
}) })
} }

View File

@ -25,7 +25,7 @@ func deleteWebhookTest(t *testing.T, s TestSetup) {
// get all webhooks // get all webhooks
webhooks, err := db.Provider.ListWebhook(ctx, model.Pagination{ webhooks, err := db.Provider.ListWebhook(ctx, model.Pagination{
Limit: 10, Limit: 20,
Page: 1, Page: 1,
Offset: 0, Offset: 0,
}) })
@ -42,7 +42,7 @@ func deleteWebhookTest(t *testing.T, s TestSetup) {
} }
webhooks, err = db.Provider.ListWebhook(ctx, model.Pagination{ webhooks, err = db.Provider.ListWebhook(ctx, model.Pagination{
Limit: 10, Limit: 20,
Page: 1, Page: 1,
Offset: 0, Offset: 0,
}) })

View File

@ -23,6 +23,8 @@ func enableAccessTest(t *testing.T, s TestSetup) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })

View File

@ -15,17 +15,18 @@ func forgotPasswordTest(t *testing.T, s TestSetup) {
t.Run(`should run forgot password`, func(t *testing.T) { t.Run(`should run forgot password`, func(t *testing.T) {
_, ctx := createContext(s) _, ctx := createContext(s)
email := "forgot_password." + s.TestInfo.Email email := "forgot_password." + s.TestInfo.Email
_, err := resolvers.SignupResolver(ctx, model.SignUpInput{ res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
_, err = resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{ assert.NotNil(t, res)
forgotPasswordRes, err := resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{
Email: email, Email: email,
}) })
assert.Nil(t, err, "no errors for forgot password") assert.Nil(t, err, "no errors for forgot password")
assert.NotNil(t, forgotPasswordRes)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -41,21 +41,20 @@ func inviteUserTest(t *testing.T, s TestSetup) {
res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{ res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
Emails: invalidEmailsTest, Emails: invalidEmailsTest,
}) })
assert.Error(t, err)
assert.Nil(t, res)
// valid test // valid test
res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{ res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
Emails: emails, Emails: emails,
}) })
assert.Nil(t, err) assert.Nil(t, err)
assert.NotNil(t, res) assert.NotNil(t, res)
// duplicate error test // duplicate error test
res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{ res, err = resolvers.InviteMembersResolver(ctx, model.InviteMemberInput{
Emails: emails, Emails: emails,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, res) assert.Nil(t, res)
cleanData(emails[0]) cleanData(emails[0])
}) })
} }

View File

@ -16,12 +16,13 @@ func loginTests(t *testing.T, s TestSetup) {
t.Run(`should login`, func(t *testing.T) { t.Run(`should login`, func(t *testing.T) {
_, ctx := createContext(s) _, ctx := createContext(s)
email := "login." + s.TestInfo.Email email := "login." + s.TestInfo.Email
_, err := resolvers.SignupResolver(ctx, model.SignUpInput{ signUpRes, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
assert.NotNil(t, signUpRes)
res, err := resolvers.LoginResolver(ctx, model.LoginInput{ res, err := resolvers.LoginResolver(ctx, model.LoginInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
@ -30,6 +31,8 @@ func loginTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err, "should fail because email is not verified") assert.NotNil(t, err, "should fail because email is not verified")
assert.Nil(t, res) assert.Nil(t, res)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
n, err := utils.EncryptNonce(verificationRequest.Nonce) n, err := utils.EncryptNonce(verificationRequest.Nonce)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, n) assert.NotEmpty(t, n)

View File

@ -20,22 +20,24 @@ func logoutTests(t *testing.T, s TestSetup) {
req, ctx := createContext(s) req, ctx := createContext(s)
email := "logout." + s.TestInfo.Email email := "logout." + s.TestInfo.Email
_, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{ magicLoginRes, err := resolvers.MagicLinkLoginResolver(ctx, model.MagicLinkLoginInput{
Email: email, Email: email,
}) })
assert.NoError(t, err)
assert.NotNil(t, magicLoginRes)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
assert.NotNil(t, verifyRes)
accessToken := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken) assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken) claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, claims) assert.NotEmpty(t, claims)
loginMethod := claims["login_method"] loginMethod := claims["login_method"]
sessionKey := verifyRes.User.ID sessionKey := verifyRes.User.ID
if loginMethod != nil && loginMethod != "" { if loginMethod != nil && loginMethod != "" {

View File

@ -30,6 +30,8 @@ func magicLinkLoginTests(t *testing.T, s TestSetup) {
assert.Nil(t, err, "signup should be successful") assert.Nil(t, err, "signup should be successful")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })

View File

@ -29,24 +29,25 @@ func mobileSingupTest(t *testing.T, s TestSetup) {
Password: "test", Password: "test",
ConfirmPassword: "test", ConfirmPassword: "test",
}) })
assert.NotNil(t, err, "invalid password") assert.Error(t, err)
assert.Nil(t, res)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email), Email: refs.NewStringRef(email),
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "singup disabled") assert.Error(t, err)
assert.Nil(t, res)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, true) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, true)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
Email: refs.NewStringRef(email), Email: refs.NewStringRef(email),
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "singup disabled") assert.Error(t, err)
assert.Nil(t, res)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, false) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableMobileBasicAuthentication, false)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
@ -54,14 +55,16 @@ func mobileSingupTest(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "invalid mobile") assert.Error(t, err)
assert.Nil(t, res)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "test", PhoneNumber: "test",
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "invalid mobile") assert.Error(t, err)
assert.Nil(t, res)
res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{ res, err = resolvers.MobileSignupResolver(ctx, &model.MobileSignUpInput{
PhoneNumber: "1234567890", PhoneNumber: "1234567890",
@ -77,7 +80,8 @@ func mobileSingupTest(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.Error(t, err, "user exists") assert.Error(t, err)
assert.Nil(t, res)
cleanData(email) cleanData(email)
cleanData("1234567890@authorizer.dev") cleanData("1234567890@authorizer.dev")

View File

@ -27,6 +27,8 @@ func profileTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })

View File

@ -44,10 +44,11 @@ func resendOTPTest(t *testing.T, s TestSetup) {
// Using access token update profile // Using access token update profile
s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken)) s.GinContext.Request.Header.Set("Authorization", "Bearer "+refs.StringValue(verifyRes.AccessToken))
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)
_, err = resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{ updateRes, err := resolvers.UpdateProfileResolver(ctx, model.UpdateProfileInput{
IsMultiFactorAuthEnabled: refs.NewBoolRef(true), IsMultiFactorAuthEnabled: refs.NewBoolRef(true),
}) })
assert.NoError(t, err)
assert.NotNil(t, updateRes)
// Resend otp should return error as no initial opt is being sent // Resend otp should return error as no initial opt is being sent
resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{ resendOtpRes, err := resolvers.ResendOTPResolver(ctx, model.ResendOTPRequest{
Email: email, Email: email,
@ -87,7 +88,7 @@ func resendOTPTest(t *testing.T, s TestSetup) {
Otp: otp.Otp, Otp: otp.Otp,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, verifyOtpRes)
verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{ verifyOtpRes, err = resolvers.VerifyOtpResolver(ctx, model.VerifyOTPRequest{
Email: email, Email: email,
Otp: newOtp.Otp, Otp: newOtp.Otp,

View File

@ -19,13 +19,12 @@ func resendVerifyEmailTests(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
_, err = resolvers.ResendVerifyEmailResolver(ctx, model.ResendVerifyEmailInput{ _, err = resolvers.ResendVerifyEmailResolver(ctx, model.ResendVerifyEmailInput{
Email: email, Email: email,
Identifier: constants.VerificationTypeBasicAuthSignup, Identifier: constants.VerificationTypeBasicAuthSignup,
}) })
assert.NoError(t, err)
assert.Nil(t, err)
cleanData(email) cleanData(email)
}) })

View File

@ -20,7 +20,7 @@ func resetPasswordTest(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
_, err = resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{ _, err = resolvers.ForgotPasswordResolver(ctx, model.ForgotPasswordInput{
Email: email, Email: email,
}) })
@ -28,7 +28,7 @@ func resetPasswordTest(t *testing.T, s TestSetup) {
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword)
assert.Nil(t, err, "should get forgot password request") assert.Nil(t, err, "should get forgot password request")
assert.NotNil(t, verificationRequest)
_, err = resolvers.ResetPasswordResolver(ctx, model.ResetPasswordInput{ _, err = resolvers.ResetPasswordResolver(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
Password: "test1", Password: "test1",

View File

@ -23,6 +23,8 @@ func revokeAccessTest(t *testing.T, s TestSetup) {
}) })
assert.NoError(t, err) assert.NoError(t, err)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
@ -33,7 +35,7 @@ func revokeAccessTest(t *testing.T, s TestSetup) {
UserID: verifyRes.User.ID, UserID: verifyRes.User.ID,
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, res)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -30,10 +30,13 @@ func sessionTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err)
assert.NotNil(t, verifyRes)
accessToken := *verifyRes.AccessToken accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken) assert.NotEmpty(t, accessToken)

View File

@ -22,14 +22,14 @@ func signupTests(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password + "s", ConfirmPassword: s.TestInfo.Password + "s",
}) })
assert.NotNil(t, err, "invalid password") assert.NotNil(t, err, "invalid password")
assert.Nil(t, res)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: "test", Password: "test",
ConfirmPassword: "test", ConfirmPassword: "test",
}) })
assert.NotNil(t, err, "invalid password") assert.NotNil(t, err, "invalid password")
assert.Nil(t, res)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, true)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
@ -37,7 +37,7 @@ func signupTests(t *testing.T, s TestSetup) {
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "singup disabled") assert.NotNil(t, err, "singup disabled")
assert.Nil(t, res)
memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false) memorystore.Provider.UpdateEnvVariable(constants.EnvKeyDisableSignUp, false)
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
@ -48,15 +48,13 @@ func signupTests(t *testing.T, s TestSetup) {
user := *res.User user := *res.User
assert.Equal(t, email, user.Email) assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil") assert.Nil(t, res.AccessToken, "access token should be nil")
res, err = resolvers.SignupResolver(ctx, model.SignUpInput{ res, err = resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NotNil(t, err, "should throw duplicate email error") assert.NotNil(t, err, "should throw duplicate email error")
assert.Nil(t, res)
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, email, verificationRequest.Email) assert.Equal(t, email, verificationRequest.Email)

View File

@ -40,31 +40,49 @@ func cleanData(email string) {
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
if err == nil { if err == nil {
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest) err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("DeleteVerificationRequest err", err)
}
} }
verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword) verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeForgotPassword)
if err == nil { if err == nil {
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest) err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("DeleteVerificationRequest err", err)
}
} }
verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeUpdateEmail) verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeUpdateEmail)
if err == nil { if err == nil {
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest) err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("DeleteVerificationRequest err", err)
}
} }
verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin) verificationRequest, err = db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeMagicLinkLogin)
if err == nil { if err == nil {
err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest) err = db.Provider.DeleteVerificationRequest(ctx, verificationRequest)
if err != nil {
log.Debug("DeleteVerificationRequest err", err)
}
} }
otp, err := db.Provider.GetOTPByEmail(ctx, email) otp, err := db.Provider.GetOTPByEmail(ctx, email)
if err == nil { if err == nil {
err = db.Provider.DeleteOTP(ctx, otp) err = db.Provider.DeleteOTP(ctx, otp)
if err != nil {
log.Debug("DeleteOTP err", err)
}
} }
dbUser, err := db.Provider.GetUserByEmail(ctx, email) dbUser, err := db.Provider.GetUserByEmail(ctx, email)
if err == nil { if err == nil {
db.Provider.DeleteUser(ctx, dbUser) err = db.Provider.DeleteUser(ctx, dbUser)
if err != nil {
log.Debug("DeleteUser err", err)
}
} }
} }

View File

@ -17,15 +17,12 @@ func updateAllUsersTest(t *testing.T, s TestSetup) {
t.Helper() t.Helper()
t.Run("Should update all users", func(t *testing.T) { t.Run("Should update all users", func(t *testing.T) {
_, ctx := createContext(s) _, ctx := createContext(s)
users := []models.User{}
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
user := models.User{ user := models.User{
Email: fmt.Sprintf("update_all_user_%d_%s", i, s.TestInfo.Email), Email: fmt.Sprintf("update_all_user_%d_%s", i, s.TestInfo.Email),
SignupMethods: constants.AuthRecipeMethodBasicAuth, SignupMethods: constants.AuthRecipeMethodBasicAuth,
Roles: "user", Roles: "user",
} }
users = append(users, user)
u, err := db.Provider.AddUser(ctx, user) u, err := db.Provider.AddUser(ctx, user)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, u) assert.NotNil(t, u)
@ -56,12 +53,15 @@ func updateAllUsersTest(t *testing.T, s TestSetup) {
Limit: 20, Limit: 20,
Offset: 0, Offset: 0,
}) })
assert.NoError(t, err)
assert.NotNil(t, listUsers)
for _, u := range listUsers.Users { for _, u := range listUsers.Users {
if utils.StringSliceContains(updateIds, u.ID) { if utils.StringSliceContains(updateIds, u.ID) {
assert.False(t, refs.BoolValue(u.IsMultiFactorAuthEnabled)) assert.False(t, refs.BoolValue(u.IsMultiFactorAuthEnabled))
} else { } else {
assert.True(t, refs.BoolValue(u.IsMultiFactorAuthEnabled)) assert.True(t, refs.BoolValue(u.IsMultiFactorAuthEnabled))
} }
cleanData(u.Email)
} }
}) })
} }

View File

@ -30,11 +30,13 @@ func updateProfileTests(t *testing.T, s TestSetup) {
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup) verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{ verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token, Token: verificationRequest.Token,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, verifyRes)
s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken) s.GinContext.Request.Header.Set("Authorization", "Bearer "+*verifyRes.AccessToken)
ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext) ctx = context.WithValue(req.Context(), "GinContextKey", s.GinContext)

View File

@ -27,7 +27,7 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, webhooks) assert.NotNil(t, webhooks)
assert.Greater(t, len(webhooks), 0) assert.Equal(t, 2, len(webhooks))
for _, webhook := range webhooks { for _, webhook := range webhooks {
// it should completely replace headers // it should completely replace headers
webhook.Headers = map[string]interface{}{ webhook.Headers = map[string]interface{}{
@ -43,18 +43,44 @@ func updateWebhookTest(t *testing.T, s TestSetup) {
assert.NotEmpty(t, res) assert.NotEmpty(t, res)
assert.NotEmpty(t, res.Message) assert.NotEmpty(t, res.Message)
} }
if len(webhooks) == 0 {
// avoid index out of range error
return
}
// Test updating webhook name
w := webhooks[0]
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: w.ID,
EventName: refs.NewStringRef(constants.UserAccessEnabledWebhookEvent),
})
assert.NoError(t, err)
assert.NotNil(t, res)
// Check if webhooks with new name is as per expected len
accessWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserAccessEnabledWebhookEvent)
assert.NoError(t, err)
assert.Equal(t, 3, len(accessWebhooks))
// Revert name change
res, err = resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: w.ID,
EventName: refs.NewStringRef(constants.UserDeletedWebhookEvent),
})
assert.NoError(t, err)
assert.NotNil(t, res)
updatedWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent) updatedWebhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserDeletedWebhookEvent)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, updatedWebhooks) assert.NotNil(t, updatedWebhooks)
assert.Equal(t, 2, len(updatedWebhooks))
for _, updatedWebhook := range updatedWebhooks { for _, updatedWebhook := range updatedWebhooks {
assert.Contains(t, refs.StringValue(updatedWebhook.EventName), constants.UserDeletedWebhookEvent) assert.Contains(t, refs.StringValue(updatedWebhook.EventName), constants.UserDeletedWebhookEvent)
assert.Len(t, updatedWebhook.Headers, 1) assert.Len(t, updatedWebhook.Headers, 1)
assert.False(t, refs.BoolValue(updatedWebhook.Enabled)) assert.False(t, refs.BoolValue(updatedWebhook.Enabled))
foundUpdatedHeader := false
for key, val := range updatedWebhook.Headers { for key, val := range updatedWebhook.Headers {
assert.Equal(t, "x-new-test", key) if key == "x-new-test" && val == "test" {
assert.Equal(t, "test", val) foundUpdatedHeader = true
} }
}
assert.True(t, foundUpdatedHeader)
assert.Equal(t, "https://sometest.com", refs.StringValue(updatedWebhook.Endpoint)) assert.Equal(t, "https://sometest.com", refs.StringValue(updatedWebhook.Endpoint))
res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{ res, err := resolvers.UpdateWebhookResolver(ctx, model.UpdateWebhookRequest{
ID: updatedWebhook.ID, ID: updatedWebhook.ID,

View File

@ -34,7 +34,7 @@ func usersTest(t *testing.T, s TestSetup) {
usersRes, err := resolvers.UsersResolver(ctx, pagination) usersRes, err := resolvers.UsersResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
assert.Nil(t, usersRes)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.Nil(t, err) assert.Nil(t, err)
h, err := crypto.EncryptPassword(adminSecret) h, err := crypto.EncryptPassword(adminSecret)

View File

@ -53,6 +53,8 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + user.ID
nonce := uuid.New().String() nonce := uuid.New().String()
authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, "") authToken, err := token.CreateAuthToken(gc, user, roles, scope, constants.AuthRecipeMethodBasicAuth, nonce, "")
assert.NoError(t, err)
assert.NotNil(t, authToken)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+authToken.FingerPrint, authToken.FingerPrintHash)
memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token) memorystore.Provider.SetUserSession(sessionKey, constants.TokenTypeAccessToken+"_"+authToken.FingerPrint, authToken.AccessToken.Token)
@ -74,8 +76,8 @@ func validateJwtTokenTest(t *testing.T, s TestSetup) {
Token: authToken.AccessToken.Token, Token: authToken.AccessToken.Token,
Roles: []string{"invalid_role"}, Roles: []string{"invalid_role"},
}) })
assert.Error(t, err) assert.Error(t, err)
assert.Nil(t, res)
}) })
t.Run(`should validate the refresh token`, func(t *testing.T) { t.Run(`should validate the refresh token`, func(t *testing.T) {

View File

@ -17,17 +17,14 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
t.Run(`should get verification requests with admin secret only`, func(t *testing.T) { t.Run(`should get verification requests with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s) req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email email := "verification_requests." + s.TestInfo.Email
res, err := resolvers.SignupResolver(ctx, model.SignUpInput{ res, err := resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email, Email: email,
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, res) assert.NotNil(t, res)
limit := int64(10) limit := int64(10)
page := int64(1) page := int64(1)
pagination := &model.PaginatedInput{ pagination := &model.PaginatedInput{
@ -39,6 +36,7 @@ func verificationRequestsTest(t *testing.T, s TestSetup) {
requests, err := resolvers.VerificationRequestsResolver(ctx, pagination) requests, err := resolvers.VerificationRequestsResolver(ctx, pagination)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
assert.Nil(t, requests)
adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) adminSecret, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -20,7 +20,8 @@ func verifyEmailTest(t *testing.T, s TestSetup) {
Password: s.TestInfo.Password, Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password, ConfirmPassword: s.TestInfo.Password,
}) })
assert.NoError(t, err)
assert.NotNil(t, res)
user := *res.User user := *res.User
assert.Equal(t, email, user.Email) assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil") assert.Nil(t, res.AccessToken, "access token should be nil")

View File

@ -29,17 +29,20 @@ func webhookLogsTest(t *testing.T, s TestSetup) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Greater(t, len(webhookLogs.WebhookLogs), 1) assert.Greater(t, len(webhookLogs.WebhookLogs), 1)
webhooks, err := resolvers.WebhooksResolver(ctx, nil) webhooks, err := resolvers.WebhooksResolver(ctx, &model.PaginatedInput{
Pagination: &model.PaginationInput{
Limit: refs.NewInt64Ref(20),
},
})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, webhooks) assert.NotEmpty(t, webhooks)
for _, w := range webhooks.Webhooks { for _, w := range webhooks.Webhooks {
t.Run(fmt.Sprintf("should get webhook for webhook_id:%s", w.ID), func(t *testing.T) { t.Run(fmt.Sprintf("should get webhook for webhook_id:%s", w.ID), func(t *testing.T) {
webhookLogs, err := resolvers.WebhookLogsResolver(ctx, &model.ListWebhookLogRequest{ webhookLogs, err := resolvers.WebhookLogsResolver(ctx, &model.ListWebhookLogRequest{
WebhookID: &w.ID, WebhookID: &w.ID,
}) })
assert.NoError(t, err) assert.NoError(t, err)
assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1) assert.GreaterOrEqual(t, len(webhookLogs.WebhookLogs), 1, refs.StringValue(w.EventName))
for _, wl := range webhookLogs.WebhookLogs { for _, wl := range webhookLogs.WebhookLogs {
assert.Equal(t, refs.StringValue(wl.WebhookID), w.ID) assert.Equal(t, refs.StringValue(wl.WebhookID), w.ID)
} }

View File

@ -28,7 +28,7 @@ func webhookTest(t *testing.T, s TestSetup) {
webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent) webhooks, err := db.Provider.GetWebhookByEventName(ctx, constants.UserCreatedWebhookEvent)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, webhooks) assert.NotNil(t, webhooks)
assert.Greater(t, len(webhooks), 0) assert.Equal(t, 2, len(webhooks))
for _, webhook := range webhooks { for _, webhook := range webhooks {
res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{ res, err := resolvers.WebhookResolver(ctx, model.WebhookRequest{
ID: webhook.ID, ID: webhook.ID,
@ -36,7 +36,7 @@ func webhookTest(t *testing.T, s TestSetup) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, res.ID, webhook.ID) assert.Equal(t, res.ID, webhook.ID)
assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint)) assert.Equal(t, refs.StringValue(res.Endpoint), refs.StringValue(webhook.Endpoint))
assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName)) // assert.Equal(t, refs.StringValue(res.EventName), refs.StringValue(webhook.EventName))
assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled)) assert.Equal(t, refs.BoolValue(res.Enabled), refs.BoolValue(webhook.Enabled))
assert.Len(t, res.Headers, len(webhook.Headers)) assert.Len(t, res.Headers, len(webhook.Headers))
} }

View File

@ -6,7 +6,9 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/crypto" "github.com/authorizerdev/authorizer/server/crypto"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore" "github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/refs"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -21,9 +23,13 @@ func webhooksTest(t *testing.T, s TestSetup) {
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h)) req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.AdminCookieName, h))
webhooks, err := resolvers.WebhooksResolver(ctx, nil) webhooks, err := resolvers.WebhooksResolver(ctx, &model.PaginatedInput{
Pagination: &model.PaginationInput{
Limit: refs.NewInt64Ref(20),
},
})
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEmpty(t, webhooks) assert.NotEmpty(t, webhooks)
assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes)) assert.Len(t, webhooks.Webhooks, len(s.TestInfo.TestWebhookEventTypes)*2)
}) })
} }

View File

@ -24,7 +24,7 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
} }
for _, webhook := range webhooks { for _, webhook := range webhooks {
if !refs.BoolValue(webhook.Enabled) { if !refs.BoolValue(webhook.Enabled) {
return nil continue
} }
userBytes, err := json.Marshal(user.AsAPIUser()) userBytes, err := json.Marshal(user.AsAPIUser())
if err != nil { if err != nil {
@ -51,30 +51,32 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
requestBody, err := json.Marshal(reqBody) requestBody, err := json.Marshal(reqBody)
if err != nil { if err != nil {
log.Debug("error marshalling requestBody obj: ", err) log.Debug("error marshalling requestBody obj: ", err)
return err continue
} }
// dont trigger webhook call in case of test // dont trigger webhook call in case of test
envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv) envKey, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyEnv)
if err != nil { if err != nil {
return err continue
} }
if envKey == constants.TestEnv { if envKey == constants.TestEnv {
db.Provider.AddWebhookLog(ctx, models.WebhookLog{ _, err := db.Provider.AddWebhookLog(ctx, models.WebhookLog{
HttpStatus: 200, HttpStatus: 200,
Request: string(requestBody), Request: string(requestBody),
Response: string(`{"message": "test"}`), Response: string(`{"message": "test"}`),
WebhookID: webhook.ID, WebhookID: webhook.ID,
}) })
if err != nil {
return nil log.Debug("error saving webhook log:", err)
}
continue
} }
requestBytesBuffer := bytes.NewBuffer(requestBody) requestBytesBuffer := bytes.NewBuffer(requestBody)
req, err := http.NewRequest("POST", refs.StringValue(webhook.Endpoint), requestBytesBuffer) req, err := http.NewRequest("POST", refs.StringValue(webhook.Endpoint), requestBytesBuffer)
if err != nil { if err != nil {
log.Debug("error creating webhook post request: ", err) log.Debug("error creating webhook post request: ", err)
return err continue
} }
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
@ -88,14 +90,14 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
log.Debug("error making request: ", err) log.Debug("error making request: ", err)
return err continue
} }
defer resp.Body.Close() defer resp.Body.Close()
responseBytes, err := ioutil.ReadAll(resp.Body) responseBytes, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
log.Debug("error reading response: ", err) log.Debug("error reading response: ", err)
return err continue
} }
statusCode := int64(resp.StatusCode) statusCode := int64(resp.StatusCode)
@ -105,10 +107,9 @@ func RegisterEvent(ctx context.Context, eventName string, authRecipe string, use
Response: string(responseBytes), Response: string(responseBytes),
WebhookID: webhook.ID, WebhookID: webhook.ID,
}) })
if err != nil { if err != nil {
log.Debug("failed to add webhook log: ", err) log.Debug("failed to add webhook log: ", err)
return err continue
} }
} }
return nil return nil