check / check (push) Waiting to run
Target.MaxRetries (max_retries) becomes Target.MaxAttempts (max_attempts), and the form field, parser and ceiling are renamed to match. The column is NOT NULL and a check constraint refuses a value below 1, so every target, whatever its type, is stored with at least 1. 1 is a single attempt with no circuit breaker; above 1 retries behind the breaker up to that many attempts. Both target forms refuse 0 and less; a blank field on the add target form gives 1, and on the edit form keeps the stored value. The target page shows the plain number, and the README's two paragraphs on 0 against 1 become one sentence. Model: opus-5-5
128 lines
3.3 KiB
Go
128 lines
3.3 KiB
Go
package database_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"sneak.berlin/go/webhooker/internal/database"
|
|
)
|
|
|
|
// TestPreloadedModelsMarshalWithoutTheirParent pins that a child's
|
|
// reference to the record it belongs to is left out of the JSON, so a
|
|
// webhook and its targets cannot marshal each other in a loop, and that
|
|
// GORM still preloads that reference, since it ignores json tags.
|
|
func TestPreloadedModelsMarshalWithoutTheirParent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := startedTestDB(t)
|
|
|
|
stored := database.Webhook{
|
|
UserID: uuid.New().String(),
|
|
Name: testWebhookName,
|
|
Entrypoints: []database.Entrypoint{{Path: uuid.New().String()}},
|
|
Targets: []database.Target{{
|
|
Name: "log",
|
|
Type: database.TargetTypeLog,
|
|
MaxAttempts: 1,
|
|
}},
|
|
}
|
|
require.NoError(t, db.Create(&stored).Error)
|
|
|
|
entrypointID := stored.Entrypoints[0].ID
|
|
targetID := stored.Targets[0].ID
|
|
|
|
var webhook database.Webhook
|
|
|
|
require.NoError(t, db.
|
|
Preload("Entrypoints.Webhook").
|
|
Preload("Targets.Webhook").
|
|
First(&webhook, "id = ?", stored.ID).Error)
|
|
|
|
require.Len(t, webhook.Entrypoints, 1)
|
|
require.Len(t, webhook.Targets, 1)
|
|
assert.Equal(t, stored.ID, webhook.Entrypoints[0].Webhook.ID)
|
|
assert.Equal(t, stored.ID, webhook.Targets[0].Webhook.ID)
|
|
|
|
encoded := marshalModel(t, webhook)
|
|
|
|
assert.Contains(t, encoded, entrypointID)
|
|
assert.Contains(t, encoded, targetID)
|
|
|
|
// Each child holds the parent's id as its webhookId, so the parent
|
|
// is looked for by its own id field.
|
|
parentIDField := `"id":"` + stored.ID + `"`
|
|
|
|
assert.NotContains(t, marshalModel(t, webhook.Entrypoints[0]), parentIDField)
|
|
assert.NotContains(t, marshalModel(t, webhook.Targets[0]), parentIDField)
|
|
|
|
var target database.Target
|
|
|
|
require.NoError(t, db.
|
|
Preload("Webhook").
|
|
First(&target, "id = ?", targetID).Error)
|
|
|
|
assert.Equal(t, stored.ID, target.Webhook.ID)
|
|
|
|
encoded = marshalModel(t, target)
|
|
|
|
assert.Contains(t, encoded, stored.ID)
|
|
assert.NotContains(t, encoded, parentIDField)
|
|
}
|
|
|
|
// TestModelsMarshalWithoutTheirParent covers the other references to a
|
|
// parent: each model is built with its parent set, and the parent's id
|
|
// must not appear in the JSON.
|
|
func TestModelsMarshalWithoutTheirParent(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
parent := database.BaseModel{ID: uuid.New().String()}
|
|
|
|
cases := []struct {
|
|
name string
|
|
model any
|
|
}{
|
|
{
|
|
name: "Webhook.User",
|
|
model: database.Webhook{User: database.User{BaseModel: parent}},
|
|
},
|
|
{
|
|
name: "APIKey.User",
|
|
model: database.APIKey{User: database.User{BaseModel: parent}},
|
|
},
|
|
{
|
|
name: "Delivery.Event",
|
|
model: database.Delivery{Event: database.Event{BaseModel: parent}},
|
|
},
|
|
{
|
|
name: "Delivery.Target",
|
|
model: database.Delivery{Target: database.Target{BaseModel: parent}},
|
|
},
|
|
{
|
|
name: "DeliveryResult.Delivery",
|
|
model: database.DeliveryResult{
|
|
Delivery: database.Delivery{BaseModel: parent},
|
|
},
|
|
},
|
|
{
|
|
name: "Event.Webhook",
|
|
model: database.Event{Webhook: database.Webhook{BaseModel: parent}},
|
|
},
|
|
{
|
|
name: "Event.Entrypoint",
|
|
model: database.Event{
|
|
Entrypoint: database.Entrypoint{BaseModel: parent},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
assert.NotContains(t, marshalModel(t, tc.model), parent.ID)
|
|
})
|
|
}
|
|
}
|