first-commit
This commit is contained in:
263
models/webhook/hooktask.go
Normal file
263
models/webhook/hooktask.go
Normal file
@@ -0,0 +1,263 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
gouuid "github.com/google/uuid"
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ___ ___ __ ___________ __
|
||||
// / | \ ____ ____ | | _\__ ___/____ _____| | __
|
||||
// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ /
|
||||
// \ Y ( <_> | <_> ) < | | / __ \_\___ \| <
|
||||
// \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \
|
||||
// \/ \/ \/ \/ \/
|
||||
|
||||
// HookRequest represents hook task request information.
|
||||
type HookRequest struct {
|
||||
URL string `json:"url"`
|
||||
HTTPMethod string `json:"http_method"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// HookResponse represents hook task response information.
|
||||
type HookResponse struct {
|
||||
Status int `json:"status"`
|
||||
Headers map[string]string `json:"headers"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
// HookTask represents a hook task.
|
||||
type HookTask struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
HookID int64 `xorm:"index"`
|
||||
UUID string `xorm:"unique"`
|
||||
PayloadContent string `xorm:"LONGTEXT"`
|
||||
// PayloadVersion number to allow for smooth version upgrades:
|
||||
// - PayloadVersion 1: PayloadContent contains the JSON as sent to the URL
|
||||
// - PayloadVersion 2: PayloadContent contains the original event
|
||||
PayloadVersion int `xorm:"DEFAULT 1"`
|
||||
|
||||
EventType webhook_module.HookEventType
|
||||
IsDelivered bool
|
||||
Delivered timeutil.TimeStampNano
|
||||
|
||||
// History info.
|
||||
IsSucceed bool
|
||||
RequestContent string `xorm:"LONGTEXT"`
|
||||
RequestInfo *HookRequest `xorm:"-"`
|
||||
ResponseContent string `xorm:"LONGTEXT"`
|
||||
ResponseInfo *HookResponse `xorm:"-"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(HookTask))
|
||||
}
|
||||
|
||||
// BeforeUpdate will be invoked by XORM before updating a record
|
||||
// representing this object
|
||||
func (t *HookTask) BeforeUpdate() {
|
||||
if t.RequestInfo != nil {
|
||||
t.RequestContent = t.simpleMarshalJSON(t.RequestInfo)
|
||||
}
|
||||
if t.ResponseInfo != nil {
|
||||
t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo)
|
||||
}
|
||||
}
|
||||
|
||||
// AfterLoad updates the webhook object upon setting a column
|
||||
func (t *HookTask) AfterLoad() {
|
||||
if len(t.RequestContent) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
t.RequestInfo = &HookRequest{}
|
||||
if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil {
|
||||
log.Error("Unmarshal RequestContent[%d]: %v", t.ID, err)
|
||||
}
|
||||
|
||||
if len(t.ResponseContent) > 0 {
|
||||
t.ResponseInfo = &HookResponse{}
|
||||
if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil {
|
||||
log.Error("Unmarshal ResponseContent[%d]: %v", t.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *HookTask) simpleMarshalJSON(v any) string {
|
||||
p, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
log.Error("Marshal [%d]: %v", t.ID, err)
|
||||
}
|
||||
return string(p)
|
||||
}
|
||||
|
||||
// HookTasks returns a list of hook tasks by given conditions.
|
||||
func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error) {
|
||||
tasks := make([]*HookTask, 0, setting.Webhook.PagingNum)
|
||||
return tasks, db.GetEngine(ctx).
|
||||
Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum).
|
||||
Where("hook_id=?", hookID).
|
||||
Desc("id").
|
||||
Find(&tasks)
|
||||
}
|
||||
|
||||
// CreateHookTask creates a new hook task,
|
||||
// it handles conversion from Payload to PayloadContent.
|
||||
func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) {
|
||||
t.UUID = gouuid.New().String()
|
||||
if t.Delivered == 0 {
|
||||
t.Delivered = timeutil.TimeStampNanoNow()
|
||||
}
|
||||
if t.PayloadVersion == 0 {
|
||||
return nil, errors.New("missing HookTask.PayloadVersion")
|
||||
}
|
||||
return t, db.Insert(ctx, t)
|
||||
}
|
||||
|
||||
func GetHookTaskByID(ctx context.Context, id int64) (*HookTask, error) {
|
||||
t := &HookTask{}
|
||||
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !has {
|
||||
return nil, ErrHookTaskNotExist{
|
||||
TaskID: id,
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// UpdateHookTask updates information of hook task.
|
||||
func UpdateHookTask(ctx context.Context, t *HookTask) error {
|
||||
_, err := db.GetEngine(ctx).ID(t.ID).AllCols().Update(t)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplayHookTask copies a hook task to get re-delivered
|
||||
func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask, error) {
|
||||
task, exist, err := db.Get[HookTask](ctx, builder.Eq{"hook_id": hookID, "uuid": uuid})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !exist {
|
||||
return nil, ErrHookTaskNotExist{
|
||||
HookID: hookID,
|
||||
UUID: uuid,
|
||||
}
|
||||
}
|
||||
|
||||
return CreateHookTask(ctx, &HookTask{
|
||||
HookID: task.HookID,
|
||||
PayloadContent: task.PayloadContent,
|
||||
EventType: task.EventType,
|
||||
PayloadVersion: task.PayloadVersion,
|
||||
})
|
||||
}
|
||||
|
||||
// FindUndeliveredHookTaskIDs will find the next 100 undelivered hook tasks with ID greater than the provided lowerID
|
||||
func FindUndeliveredHookTaskIDs(ctx context.Context, lowerID int64) ([]int64, error) {
|
||||
const batchSize = 100
|
||||
|
||||
tasks := make([]int64, 0, batchSize)
|
||||
return tasks, db.GetEngine(ctx).
|
||||
Select("id").
|
||||
Table(new(HookTask)).
|
||||
Where("is_delivered=?", false).
|
||||
And("id > ?", lowerID).
|
||||
Asc("id").
|
||||
Limit(batchSize).
|
||||
Find(&tasks)
|
||||
}
|
||||
|
||||
func MarkTaskDelivered(ctx context.Context, task *HookTask) (bool, error) {
|
||||
count, err := db.GetEngine(ctx).ID(task.ID).Where("is_delivered = ?", false).Cols("is_delivered").Update(&HookTask{
|
||||
ID: task.ID,
|
||||
IsDelivered: true,
|
||||
})
|
||||
|
||||
return count != 0, err
|
||||
}
|
||||
|
||||
// CleanupHookTaskTable deletes rows from hook_task as needed.
|
||||
func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error {
|
||||
log.Trace("Doing: CleanupHookTaskTable")
|
||||
|
||||
switch cleanupType {
|
||||
case OlderThan:
|
||||
deleteOlderThan := time.Now().Add(-olderThan).UnixNano()
|
||||
deletes, err := db.GetEngine(ctx).
|
||||
Where("is_delivered = ? and delivered < ?", true, deleteOlderThan).
|
||||
Delete(new(HookTask))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace("Deleted %d rows from hook_task", deletes)
|
||||
case PerWebhook:
|
||||
hookIDs := make([]int64, 0, 10)
|
||||
err := db.GetEngine(ctx).
|
||||
Table("webhook").
|
||||
Where("id > 0").
|
||||
Cols("id").
|
||||
Find(&hookIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hookID := range hookIDs {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return db.ErrCancelledf("Before deleting hook_task records for hook id %d", hookID)
|
||||
default:
|
||||
}
|
||||
if err = deleteDeliveredHookTasksByWebhook(ctx, hookID, numberToKeep); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Trace("Finished: CleanupHookTaskTable")
|
||||
return nil
|
||||
}
|
||||
|
||||
func deleteDeliveredHookTasksByWebhook(ctx context.Context, hookID int64, numberDeliveriesToKeep int) error {
|
||||
log.Trace("Deleting hook_task rows for webhook %d, keeping the most recent %d deliveries", hookID, numberDeliveriesToKeep)
|
||||
deliveryDates := make([]int64, 0, 10)
|
||||
err := db.GetEngine(ctx).Table("hook_task").
|
||||
Where("hook_task.hook_id = ? AND hook_task.is_delivered = ? AND hook_task.delivered is not null", hookID, true).
|
||||
Cols("hook_task.delivered").
|
||||
Join("INNER", "webhook", "hook_task.hook_id = webhook.id").
|
||||
OrderBy("hook_task.delivered desc").
|
||||
Limit(1, numberDeliveriesToKeep).
|
||||
Find(&deliveryDates)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(deliveryDates) > 0 {
|
||||
deletes, err := db.GetEngine(ctx).
|
||||
Where("hook_id = ? and is_delivered = ? and delivered <= ?", hookID, true, deliveryDates[0]).
|
||||
Delete(new(HookTask))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Trace("Deleted %d hook_task rows for webhook %d", deletes, hookID)
|
||||
} else {
|
||||
log.Trace("No hook_task rows to delete for webhook %d", hookID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
19
models/webhook/main_test.go
Normal file
19
models/webhook/main_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2020 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
unittest.MainTest(m, &unittest.TestOptions{
|
||||
FixtureFiles: []string{
|
||||
"webhook.yml",
|
||||
"hook_task.yml",
|
||||
},
|
||||
})
|
||||
}
|
353
models/webhook/webhook.go
Normal file
353
models/webhook/webhook.go
Normal file
@@ -0,0 +1,353 @@
|
||||
// Copyright 2014 The Gogs Authors. All rights reserved.
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/secret"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
// ErrWebhookNotExist represents a "WebhookNotExist" kind of error.
|
||||
type ErrWebhookNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
|
||||
func IsErrWebhookNotExist(err error) bool {
|
||||
_, ok := err.(ErrWebhookNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrWebhookNotExist) Error() string {
|
||||
return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
|
||||
}
|
||||
|
||||
func (err ErrWebhookNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
|
||||
type ErrHookTaskNotExist struct {
|
||||
TaskID int64
|
||||
HookID int64
|
||||
UUID string
|
||||
}
|
||||
|
||||
// IsErrHookTaskNotExist checks if an error is a ErrHookTaskNotExist.
|
||||
func IsErrHookTaskNotExist(err error) bool {
|
||||
_, ok := err.(ErrHookTaskNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrHookTaskNotExist) Error() string {
|
||||
return fmt.Sprintf("hook task does not exist [task: %d, hook: %d, uuid: %s]", err.TaskID, err.HookID, err.UUID)
|
||||
}
|
||||
|
||||
func (err ErrHookTaskNotExist) Unwrap() error {
|
||||
return util.ErrNotExist
|
||||
}
|
||||
|
||||
// HookContentType is the content type of a web hook
|
||||
type HookContentType int
|
||||
|
||||
const (
|
||||
// ContentTypeJSON is a JSON payload for web hooks
|
||||
ContentTypeJSON HookContentType = iota + 1
|
||||
// ContentTypeForm is an url-encoded form payload for web hook
|
||||
ContentTypeForm
|
||||
)
|
||||
|
||||
var hookContentTypes = map[string]HookContentType{
|
||||
"json": ContentTypeJSON,
|
||||
"form": ContentTypeForm,
|
||||
}
|
||||
|
||||
// ToHookContentType returns HookContentType by given name.
|
||||
func ToHookContentType(name string) HookContentType {
|
||||
return hookContentTypes[name]
|
||||
}
|
||||
|
||||
// HookTaskCleanupType is the type of cleanup to perform on hook_task
|
||||
type HookTaskCleanupType int
|
||||
|
||||
const (
|
||||
// OlderThan hook_task rows will be cleaned up by the age of the row
|
||||
OlderThan HookTaskCleanupType = iota
|
||||
// PerWebhook hook_task rows will be cleaned up by leaving the most recent deliveries for each webhook
|
||||
PerWebhook
|
||||
)
|
||||
|
||||
var hookTaskCleanupTypes = map[string]HookTaskCleanupType{
|
||||
"OlderThan": OlderThan,
|
||||
"PerWebhook": PerWebhook,
|
||||
}
|
||||
|
||||
// ToHookTaskCleanupType returns HookTaskCleanupType by given name.
|
||||
func ToHookTaskCleanupType(name string) HookTaskCleanupType {
|
||||
return hookTaskCleanupTypes[name]
|
||||
}
|
||||
|
||||
// Name returns the name of a given web hook's content type
|
||||
func (t HookContentType) Name() string {
|
||||
switch t {
|
||||
case ContentTypeJSON:
|
||||
return "json"
|
||||
case ContentTypeForm:
|
||||
return "form"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// IsValidHookContentType returns true if given name is a valid hook content type.
|
||||
func IsValidHookContentType(name string) bool {
|
||||
_, ok := hookContentTypes[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// Webhook represents a web hook object.
|
||||
type Webhook struct {
|
||||
ID int64 `xorm:"pk autoincr"`
|
||||
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
|
||||
OwnerID int64 `xorm:"INDEX"`
|
||||
IsSystemWebhook bool
|
||||
URL string `xorm:"url TEXT"`
|
||||
HTTPMethod string `xorm:"http_method"`
|
||||
ContentType HookContentType
|
||||
Secret string `xorm:"TEXT"`
|
||||
Events string `xorm:"TEXT"`
|
||||
*webhook_module.HookEvent `xorm:"-"`
|
||||
IsActive bool `xorm:"INDEX"`
|
||||
Type webhook_module.HookType `xorm:"VARCHAR(16) 'type'"`
|
||||
Meta string `xorm:"TEXT"` // store hook-specific attributes
|
||||
LastStatus webhook_module.HookStatus // Last delivery status
|
||||
|
||||
// HeaderAuthorizationEncrypted should be accessed using HeaderAuthorization() and SetHeaderAuthorization()
|
||||
HeaderAuthorizationEncrypted string `xorm:"TEXT"`
|
||||
|
||||
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
db.RegisterModel(new(Webhook))
|
||||
}
|
||||
|
||||
// AfterLoad updates the webhook object upon setting a column
|
||||
func (w *Webhook) AfterLoad() {
|
||||
w.HookEvent = &webhook_module.HookEvent{}
|
||||
if err := json.Unmarshal([]byte(w.Events), w.HookEvent); err != nil {
|
||||
log.Error("Unmarshal[%d]: %v", w.ID, err)
|
||||
}
|
||||
}
|
||||
|
||||
// History returns history of webhook by given conditions.
|
||||
func (w *Webhook) History(ctx context.Context, page int) ([]*HookTask, error) {
|
||||
return HookTasks(ctx, w.ID, page)
|
||||
}
|
||||
|
||||
// UpdateEvent handles conversion from HookEvent to Events.
|
||||
func (w *Webhook) UpdateEvent() error {
|
||||
data, err := json.Marshal(w.HookEvent)
|
||||
w.Events = string(data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (w *Webhook) HasEvent(evt webhook_module.HookEventType) bool {
|
||||
if w.SendEverything {
|
||||
return true
|
||||
}
|
||||
if w.PushOnly {
|
||||
return evt == webhook_module.HookEventPush
|
||||
}
|
||||
checkEvt := evt
|
||||
switch evt {
|
||||
case webhook_module.HookEventPullRequestReviewApproved, webhook_module.HookEventPullRequestReviewRejected, webhook_module.HookEventPullRequestReviewComment:
|
||||
checkEvt = webhook_module.HookEventPullRequestReview
|
||||
}
|
||||
return w.HookEvents[checkEvt]
|
||||
}
|
||||
|
||||
// EventsArray returns an array of hook events
|
||||
func (w *Webhook) EventsArray() []string {
|
||||
if w.SendEverything {
|
||||
events := make([]string, 0, len(webhook_module.AllEvents()))
|
||||
for _, evt := range webhook_module.AllEvents() {
|
||||
events = append(events, string(evt))
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
if w.PushOnly {
|
||||
return []string{string(webhook_module.HookEventPush)}
|
||||
}
|
||||
|
||||
events := make([]string, 0, len(w.HookEvents))
|
||||
for event, enabled := range w.HookEvents {
|
||||
if enabled {
|
||||
events = append(events, string(event))
|
||||
}
|
||||
}
|
||||
return events
|
||||
}
|
||||
|
||||
// HeaderAuthorization returns the decrypted Authorization header.
|
||||
// Not on the reference (*w), to be accessible on WebhooksNew.
|
||||
func (w Webhook) HeaderAuthorization() (string, error) {
|
||||
if w.HeaderAuthorizationEncrypted == "" {
|
||||
return "", nil
|
||||
}
|
||||
return secret.DecryptSecret(setting.SecretKey, w.HeaderAuthorizationEncrypted)
|
||||
}
|
||||
|
||||
// SetHeaderAuthorization encrypts and sets the Authorization header.
|
||||
func (w *Webhook) SetHeaderAuthorization(cleartext string) error {
|
||||
if cleartext == "" {
|
||||
w.HeaderAuthorizationEncrypted = ""
|
||||
return nil
|
||||
}
|
||||
ciphertext, err := secret.EncryptSecret(setting.SecretKey, cleartext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.HeaderAuthorizationEncrypted = ciphertext
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateWebhook creates a new web hook.
|
||||
func CreateWebhook(ctx context.Context, w *Webhook) error {
|
||||
w.Type = strings.TrimSpace(w.Type)
|
||||
return db.Insert(ctx, w)
|
||||
}
|
||||
|
||||
// CreateWebhooks creates multiple web hooks
|
||||
func CreateWebhooks(ctx context.Context, ws []*Webhook) error {
|
||||
// xorm returns err "no element on slice when insert" for empty slices.
|
||||
if len(ws) == 0 {
|
||||
return nil
|
||||
}
|
||||
for i := range ws {
|
||||
ws[i].Type = strings.TrimSpace(ws[i].Type)
|
||||
}
|
||||
return db.Insert(ctx, ws)
|
||||
}
|
||||
|
||||
// GetWebhookByID returns webhook of repository by given ID.
|
||||
func GetWebhookByID(ctx context.Context, id int64) (*Webhook, error) {
|
||||
bean := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).ID(id).Get(bean)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return bean, nil
|
||||
}
|
||||
|
||||
// GetWebhookByRepoID returns webhook of repository by given ID.
|
||||
func GetWebhookByRepoID(ctx context.Context, repoID, id int64) (*Webhook, error) {
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND repo_id=?", id, repoID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// GetWebhookByOwnerID returns webhook of a user or organization by given ID.
|
||||
func GetWebhookByOwnerID(ctx context.Context, ownerID, id int64) (*Webhook, error) {
|
||||
webhook := new(Webhook)
|
||||
has, err := db.GetEngine(ctx).Where("id=? AND owner_id=?", id, ownerID).Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// ListWebhookOptions are options to filter webhooks on ListWebhooksByOpts
|
||||
type ListWebhookOptions struct {
|
||||
db.ListOptions
|
||||
RepoID int64
|
||||
OwnerID int64
|
||||
IsActive optional.Option[bool]
|
||||
}
|
||||
|
||||
func (opts ListWebhookOptions) ToConds() builder.Cond {
|
||||
cond := builder.NewCond()
|
||||
if opts.RepoID != 0 {
|
||||
cond = cond.And(builder.Eq{"webhook.repo_id": opts.RepoID})
|
||||
}
|
||||
if opts.OwnerID != 0 {
|
||||
cond = cond.And(builder.Eq{"webhook.owner_id": opts.OwnerID})
|
||||
}
|
||||
if opts.IsActive.Has() {
|
||||
cond = cond.And(builder.Eq{"webhook.is_active": opts.IsActive.Value()})
|
||||
}
|
||||
return cond
|
||||
}
|
||||
|
||||
// UpdateWebhook updates information of webhook.
|
||||
func UpdateWebhook(ctx context.Context, w *Webhook) error {
|
||||
_, err := db.GetEngine(ctx).ID(w.ID).AllCols().Update(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateWebhookLastStatus updates last status of webhook.
|
||||
func UpdateWebhookLastStatus(ctx context.Context, w *Webhook) error {
|
||||
_, err := db.GetEngine(ctx).ID(w.ID).Cols("last_status").Update(w)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteWebhookByID uses argument bean as query condition,
|
||||
// ID must be specified and do not assign unnecessary fields.
|
||||
func DeleteWebhookByID(ctx context.Context, id int64) (err error) {
|
||||
ctx, committer, err := db.TxContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer committer.Close()
|
||||
|
||||
if count, err := db.DeleteByID[Webhook](ctx, id); err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return ErrWebhookNotExist{ID: id}
|
||||
} else if _, err = db.DeleteByBean(ctx, &HookTask{HookID: id}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return committer.Commit()
|
||||
}
|
||||
|
||||
// DeleteWebhookByRepoID deletes webhook of repository by given ID.
|
||||
func DeleteWebhookByRepoID(ctx context.Context, repoID, id int64) error {
|
||||
if _, err := GetWebhookByRepoID(ctx, repoID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(ctx, id)
|
||||
}
|
||||
|
||||
// DeleteWebhookByOwnerID deletes webhook of a user or organization by given ID.
|
||||
func DeleteWebhookByOwnerID(ctx context.Context, ownerID, id int64) error {
|
||||
if _, err := GetWebhookByOwnerID(ctx, ownerID, id); err != nil {
|
||||
return err
|
||||
}
|
||||
return DeleteWebhookByID(ctx, id)
|
||||
}
|
94
models/webhook/webhook_system.go
Normal file
94
models/webhook/webhook_system.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
)
|
||||
|
||||
// GetSystemOrDefaultWebhooks returns webhooks by given argument or all if argument is missing.
|
||||
func GetSystemOrDefaultWebhooks(ctx context.Context, isSystemWebhook optional.Option[bool]) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
if !isSystemWebhook.Has() {
|
||||
return webhooks, db.GetEngine(ctx).Where("repo_id=? AND owner_id=?", 0, 0).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
return webhooks, db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, isSystemWebhook.Value()).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
// GetDefaultWebhooks returns all admin-default webhooks.
|
||||
func GetDefaultWebhooks(ctx context.Context) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
return webhooks, db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, false).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
// GetSystemOrDefaultWebhook returns admin system or default webhook by given ID.
|
||||
func GetSystemOrDefaultWebhook(ctx context.Context, id int64) (*Webhook, error) {
|
||||
webhook := &Webhook{ID: id}
|
||||
has, err := db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=?", 0, 0).
|
||||
Get(webhook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
return webhook, nil
|
||||
}
|
||||
|
||||
// GetSystemWebhooks returns all admin system webhooks.
|
||||
func GetSystemWebhooks(ctx context.Context, isActive optional.Option[bool]) ([]*Webhook, error) {
|
||||
webhooks := make([]*Webhook, 0, 5)
|
||||
if !isActive.Has() {
|
||||
return webhooks, db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=?", 0, 0, true).
|
||||
Find(&webhooks)
|
||||
}
|
||||
return webhooks, db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=? AND is_system_webhook=? AND is_active = ?", 0, 0, true, isActive.Value()).
|
||||
Find(&webhooks)
|
||||
}
|
||||
|
||||
// DeleteDefaultSystemWebhook deletes an admin-configured default or system webhook (where Org and Repo ID both 0)
|
||||
func DeleteDefaultSystemWebhook(ctx context.Context, id int64) error {
|
||||
return db.WithTx(ctx, func(ctx context.Context) error {
|
||||
count, err := db.GetEngine(ctx).
|
||||
Where("repo_id=? AND owner_id=?", 0, 0).
|
||||
Delete(&Webhook{ID: id})
|
||||
if err != nil {
|
||||
return err
|
||||
} else if count == 0 {
|
||||
return ErrWebhookNotExist{ID: id}
|
||||
}
|
||||
|
||||
_, err = db.DeleteByBean(ctx, &HookTask{HookID: id})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo
|
||||
func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error {
|
||||
ws, err := GetDefaultWebhooks(ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("GetDefaultWebhooks: %v", err)
|
||||
}
|
||||
|
||||
for _, w := range ws {
|
||||
w.ID = 0
|
||||
w.RepoID = repoID
|
||||
if err := CreateWebhook(ctx, w); err != nil {
|
||||
return fmt.Errorf("CreateWebhook: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
37
models/webhook/webhook_system_test.go
Normal file
37
models/webhook/webhook_system_test.go
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSystemOrDefaultWebhooks(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
hooks, err := GetSystemOrDefaultWebhooks(db.DefaultContext, optional.None[bool]())
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 2) {
|
||||
assert.Equal(t, int64(5), hooks[0].ID)
|
||||
assert.Equal(t, int64(6), hooks[1].ID)
|
||||
}
|
||||
|
||||
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(true))
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(5), hooks[0].ID)
|
||||
}
|
||||
|
||||
hooks, err = GetSystemOrDefaultWebhooks(db.DefaultContext, optional.Some(false))
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(6), hooks[0].ID)
|
||||
}
|
||||
}
|
332
models/webhook/webhook_test.go
Normal file
332
models/webhook/webhook_test.go
Normal file
@@ -0,0 +1,332 @@
|
||||
// Copyright 2017 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"code.gitea.io/gitea/models/db"
|
||||
"code.gitea.io/gitea/models/unittest"
|
||||
"code.gitea.io/gitea/modules/json"
|
||||
"code.gitea.io/gitea/modules/optional"
|
||||
"code.gitea.io/gitea/modules/timeutil"
|
||||
webhook_module "code.gitea.io/gitea/modules/webhook"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHookContentType_Name(t *testing.T) {
|
||||
assert.Equal(t, "json", ContentTypeJSON.Name())
|
||||
assert.Equal(t, "form", ContentTypeForm.Name())
|
||||
}
|
||||
|
||||
func TestIsValidHookContentType(t *testing.T) {
|
||||
assert.True(t, IsValidHookContentType("json"))
|
||||
assert.True(t, IsValidHookContentType("form"))
|
||||
assert.False(t, IsValidHookContentType("invalid"))
|
||||
}
|
||||
|
||||
func TestWebhook_History(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
|
||||
tasks, err := webhook.History(db.DefaultContext, 0)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, tasks, 3) {
|
||||
assert.Equal(t, int64(3), tasks[0].ID)
|
||||
assert.Equal(t, int64(2), tasks[1].ID)
|
||||
assert.Equal(t, int64(1), tasks[2].ID)
|
||||
}
|
||||
|
||||
webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
|
||||
tasks, err = webhook.History(db.DefaultContext, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, tasks)
|
||||
}
|
||||
|
||||
func TestWebhook_UpdateEvent(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
|
||||
hookEvent := &webhook_module.HookEvent{
|
||||
PushOnly: true,
|
||||
SendEverything: false,
|
||||
ChooseEvents: false,
|
||||
HookEvents: webhook_module.HookEvents{
|
||||
webhook_module.HookEventCreate: false,
|
||||
webhook_module.HookEventPush: true,
|
||||
webhook_module.HookEventPullRequest: false,
|
||||
},
|
||||
}
|
||||
webhook.HookEvent = hookEvent
|
||||
assert.NoError(t, webhook.UpdateEvent())
|
||||
assert.NotEmpty(t, webhook.Events)
|
||||
actualHookEvent := &webhook_module.HookEvent{}
|
||||
assert.NoError(t, json.Unmarshal([]byte(webhook.Events), actualHookEvent))
|
||||
assert.Equal(t, *hookEvent, *actualHookEvent)
|
||||
}
|
||||
|
||||
func TestWebhook_EventsArray(t *testing.T) {
|
||||
assert.Equal(t, []string{
|
||||
"create", "delete", "fork", "push",
|
||||
"issues", "issue_assign", "issue_label", "issue_milestone", "issue_comment",
|
||||
"pull_request", "pull_request_assign", "pull_request_label", "pull_request_milestone",
|
||||
"pull_request_comment", "pull_request_review_approved", "pull_request_review_rejected",
|
||||
"pull_request_review_comment", "pull_request_sync", "pull_request_review_request", "wiki", "repository", "release",
|
||||
"package", "status", "workflow_run", "workflow_job",
|
||||
},
|
||||
(&Webhook{
|
||||
HookEvent: &webhook_module.HookEvent{SendEverything: true},
|
||||
}).EventsArray(),
|
||||
)
|
||||
|
||||
assert.Equal(t, []string{"push"},
|
||||
(&Webhook{
|
||||
HookEvent: &webhook_module.HookEvent{PushOnly: true},
|
||||
}).EventsArray(),
|
||||
)
|
||||
}
|
||||
|
||||
func TestCreateWebhook(t *testing.T) {
|
||||
hook := &Webhook{
|
||||
RepoID: 3,
|
||||
URL: "https://www.example.com/unit_test",
|
||||
ContentType: ContentTypeJSON,
|
||||
Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
assert.NoError(t, CreateWebhook(db.DefaultContext, hook))
|
||||
unittest.AssertExistsAndLoadBean(t, hook)
|
||||
}
|
||||
|
||||
func TestGetWebhookByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hook, err := GetWebhookByRepoID(db.DefaultContext, 1, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(1), hook.ID)
|
||||
|
||||
_, err = GetWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
func TestGetWebhookByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hook, err := GetWebhookByOwnerID(db.DefaultContext, 3, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), hook.ID)
|
||||
|
||||
_, err = GetWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
func TestGetActiveWebhooksByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1, IsActive: optional.Some(true)})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(1), hooks[0].ID)
|
||||
assert.True(t, hooks[0].IsActive)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWebhooksByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{RepoID: 1})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 2) {
|
||||
assert.Equal(t, int64(1), hooks[0].ID)
|
||||
assert.Equal(t, int64(2), hooks[1].ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetActiveWebhooksByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3, IsActive: optional.Some(true)})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(3), hooks[0].ID)
|
||||
assert.True(t, hooks[0].IsActive)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetWebhooksByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hooks, err := db.Find[Webhook](db.DefaultContext, ListWebhookOptions{OwnerID: 3})
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hooks, 1) {
|
||||
assert.Equal(t, int64(3), hooks[0].ID)
|
||||
assert.True(t, hooks[0].IsActive)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateWebhook(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
|
||||
hook.IsActive = true
|
||||
hook.ContentType = ContentTypeForm
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
assert.NoError(t, UpdateWebhook(db.DefaultContext, hook))
|
||||
unittest.AssertExistsAndLoadBean(t, hook)
|
||||
}
|
||||
|
||||
func TestDeleteWebhookByRepoID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2, RepoID: 1})
|
||||
assert.NoError(t, DeleteWebhookByRepoID(db.DefaultContext, 1, 2))
|
||||
unittest.AssertNotExistsBean(t, &Webhook{ID: 2, RepoID: 1})
|
||||
|
||||
err := DeleteWebhookByRepoID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
func TestDeleteWebhookByOwnerID(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 3, OwnerID: 3})
|
||||
assert.NoError(t, DeleteWebhookByOwnerID(db.DefaultContext, 3, 3))
|
||||
unittest.AssertNotExistsBean(t, &Webhook{ID: 3, OwnerID: 3})
|
||||
|
||||
err := DeleteWebhookByOwnerID(db.DefaultContext, unittest.NonexistentID, unittest.NonexistentID)
|
||||
assert.Error(t, err)
|
||||
assert.True(t, IsErrWebhookNotExist(err))
|
||||
}
|
||||
|
||||
func TestHookTasks(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTasks, err := HookTasks(db.DefaultContext, 1, 1)
|
||||
assert.NoError(t, err)
|
||||
if assert.Len(t, hookTasks, 3) {
|
||||
assert.Equal(t, int64(3), hookTasks[0].ID)
|
||||
assert.Equal(t, int64(2), hookTasks[1].ID)
|
||||
assert.Equal(t, int64(1), hookTasks[2].ID)
|
||||
}
|
||||
|
||||
hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, hookTasks)
|
||||
}
|
||||
|
||||
func TestCreateHookTask(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 3,
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestUpdateHookTask(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
|
||||
hook := unittest.AssertExistsAndLoadBean(t, &HookTask{ID: 1})
|
||||
hook.PayloadContent = "new payload content"
|
||||
hook.IsDelivered = true
|
||||
unittest.AssertNotExistsBean(t, hook)
|
||||
assert.NoError(t, UpdateHookTask(db.DefaultContext, hook))
|
||||
unittest.AssertExistsAndLoadBean(t, hook)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 3,
|
||||
IsDelivered: true,
|
||||
Delivered: timeutil.TimeStampNanoNow(),
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0))
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 4,
|
||||
IsDelivered: false,
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 0))
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 4,
|
||||
IsDelivered: true,
|
||||
Delivered: timeutil.TimeStampNanoNow(),
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), PerWebhook, 168*time.Hour, 1))
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 3,
|
||||
IsDelivered: true,
|
||||
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 4,
|
||||
IsDelivered: false,
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
}
|
||||
|
||||
func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *testing.T) {
|
||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||
hookTask := &HookTask{
|
||||
HookID: 4,
|
||||
IsDelivered: true,
|
||||
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
|
||||
PayloadVersion: 2,
|
||||
}
|
||||
unittest.AssertNotExistsBean(t, hookTask)
|
||||
_, err := CreateHookTask(db.DefaultContext, hookTask)
|
||||
assert.NoError(t, err)
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
|
||||
assert.NoError(t, CleanupHookTaskTable(t.Context(), OlderThan, 168*time.Hour, 0))
|
||||
unittest.AssertExistsAndLoadBean(t, hookTask)
|
||||
}
|
Reference in New Issue
Block a user