Files
go-roots/events/validated_event_test.go
2026-05-22 10:08:10 -04:00

217 lines
6.2 KiB
Go

package events
import (
"encoding/json"
"github.com/stretchr/testify/assert"
"testing"
)
var testEventWithTags = NewEvent(
WithID("c9a0f84fcaa889654da8992105eb122eb210c8cbd58210609a5ef7e170b51400"),
WithPubKey(testPK),
WithCreatedAt(1760740551),
WithKind(1),
WithTag(Tag{"a", "value"}),
WithTag(Tag{"b", "value", "optional"}),
WithContent("valid event"),
WithSig("668a715f1eb983172acf230d17bd283daedb2598adf8de4290bcc7eb0b802fdb60669d1e7d1104ac70393f4dbccd07e8abf897152af6ce6c0a75499874e27f14"),
)
func TestValidatedEventConstruction(t *testing.T) {
t.Run("accepts valid event", func(t *testing.T) {
_, err := NewValidatedEvent(testEvent)
assert.NoError(t, err)
})
t.Run("rejects invalid structure", func(t *testing.T) {
bad := NewEvent(
WithID(testEvent.ID),
WithPubKey("notavalidpubkey"),
WithCreatedAt(testEvent.CreatedAt),
WithKind(testEvent.Kind),
WithContent(testEvent.Content),
WithSig(testEvent.Sig),
)
_, err := NewValidatedEvent(bad)
assert.ErrorContains(t, err, "public key must be 64 lowercase hex characters")
})
t.Run("rejects id mismatch", func(t *testing.T) {
bad := NewEvent(
WithID("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
WithPubKey(testEvent.PubKey),
WithCreatedAt(testEvent.CreatedAt),
WithKind(testEvent.Kind),
WithContent(testEvent.Content),
WithSig(testEvent.Sig),
)
_, err := NewValidatedEvent(bad)
assert.ErrorContains(t, err, "does not match computed id")
})
t.Run("rejects invalid signature", func(t *testing.T) {
bad := NewEvent(
WithID(testEvent.ID),
WithPubKey(testEvent.PubKey),
WithCreatedAt(testEvent.CreatedAt),
WithKind(testEvent.Kind),
WithContent(testEvent.Content),
WithSig("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
)
_, err := NewValidatedEvent(bad)
assert.ErrorContains(t, err, "event signature is invalid")
})
}
func TestValidatedEventAccessors(t *testing.T) {
ve, err := NewValidatedEvent(testEventWithTags)
assert.NoError(t, err)
t.Run("ID", func(t *testing.T) {
assert.Equal(t, testEventWithTags.ID, ve.ID())
})
t.Run("PubKey", func(t *testing.T) {
assert.Equal(t, testEventWithTags.PubKey, ve.PubKey())
})
t.Run("CreatedAt", func(t *testing.T) {
assert.Equal(t, testEventWithTags.CreatedAt, ve.CreatedAt())
})
t.Run("Kind", func(t *testing.T) {
assert.Equal(t, testEventWithTags.Kind, ve.Kind())
})
t.Run("Content", func(t *testing.T) {
assert.Equal(t, testEventWithTags.Content, ve.Content())
})
t.Run("Sig", func(t *testing.T) {
assert.Equal(t, testEventWithTags.Sig, ve.Sig())
})
t.Run("Tags", func(t *testing.T) {
assert.Equal(t, testEventWithTags.Tags, ve.Tags())
})
}
func TestValidatedEventTagsImmutability(t *testing.T) {
t.Run("outer slice mutation is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
tags := ve.Tags()
tags[0] = Tag{"z", "injected"}
assert.Equal(t, Tag{"a", "value"}, ve.Tags()[0])
})
t.Run("inner slice mutation is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
tags := ve.Tags()
tags[0][1] = "mutated"
assert.Equal(t, "value", ve.Tags()[0][1])
})
t.Run("append to outer slice is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
originalLen := len(ve.Tags())
tags := ve.Tags()
tags = append(tags, Tag{"new", "tag"})
_ = tags
assert.Equal(t, originalLen, len(ve.Tags()))
})
t.Run("append to inner slice is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
originalLen := len(ve.Tags()[1])
tags := ve.Tags()
tags[1] = append(tags[1], "extra")
assert.Equal(t, originalLen, len(ve.Tags()[1]))
})
t.Run("successive calls return independent copies", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
first := ve.Tags()
second := ve.Tags()
first[0][0] = "z"
assert.Equal(t, "a", second[0][0])
})
t.Run("nil tags remain nil", func(t *testing.T) {
nil_tags_event := Event{
ID: testEvent.ID,
PubKey: testEvent.PubKey,
CreatedAt: testEvent.CreatedAt,
Kind: testEvent.Kind,
Tags: nil,
Content: testEvent.Content,
Sig: testEvent.Sig,
}
ve, err := NewValidatedEvent(nil_tags_event)
assert.NoError(t, err)
assert.Nil(t, ve.Tags())
assert.Nil(t, ve.Event().Tags)
})
}
func TestValidatedEventEventImmutability(t *testing.T) {
t.Run("outer tags mutation is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
e := ve.Event()
e.Tags[0] = Tag{"z", "injected"}
assert.Equal(t, Tag{"a", "value"}, ve.Tags()[0])
})
t.Run("inner tags mutation is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
e := ve.Event()
e.Tags[0][1] = "mutated"
assert.Equal(t, "value", ve.Tags()[0][1])
})
t.Run("scalar field mutation is isolated", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEvent)
e := ve.Event()
e.Content = "tampered"
assert.Equal(t, testEvent.Content, ve.Content())
})
t.Run("successive calls return independent copies", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
first := ve.Event()
second := ve.Event()
first.Tags[0][0] = "z"
assert.Equal(t, "a", second.Tags[0][0])
})
t.Run("returned event passes validation", func(t *testing.T) {
ve, _ := NewValidatedEvent(testEventWithTags)
assert.NoError(t, Validate(ve.Event()))
})
t.Run("source event mutation after construction is isolated", func(t *testing.T) {
source := NewEvent(
WithID(testEventWithTags.ID),
WithPubKey(testEventWithTags.PubKey),
WithCreatedAt(testEventWithTags.CreatedAt),
WithKind(testEventWithTags.Kind),
WithTag(Tag{"a", "value"}),
WithTag(Tag{"b", "value", "optional"}),
WithContent(testEventWithTags.Content),
WithSig(testEventWithTags.Sig),
)
ve, err := NewValidatedEvent(source)
assert.NoError(t, err)
source.Tags[0][1] = "mutated"
source.Content = "tampered"
assert.Equal(t, "value", ve.Tags()[0][1])
assert.Equal(t, testEventWithTags.Content, ve.Content())
})
}
func TestValidatedEventMarshalJSON(t *testing.T) {
ve, _ := NewValidatedEvent(testEvent)
jsonBytes, err := json.Marshal(ve)
assert.NoError(t, err)
assert.Equal(t, testEventJSON, string(jsonBytes))
}