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, }}, } 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) }) } }