* fix(ap): add safe constructors, getters, and validators to ActivityPub actors to address #4701 * chore: replace nil check with the new Validate() method * chore(test): added test to verify the values extracted from actors * fix: return the specific error type + test for it * fix(ap): add recovery for each AP inbox worker for a worst case scenario * fix(ap): add additional safe accessor methods to other AP entities other than actors * chore(tests): add tests for the new safe accessors * fix(ap): handle empty public keys in AP actors
252 lines
7.4 KiB
Go
252 lines
7.4 KiB
Go
package inbox
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"testing"
|
|
|
|
"github.com/go-fed/activity/streams"
|
|
)
|
|
|
|
func mustParseURL(s string) *url.URL {
|
|
u, err := url.Parse(s)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
// These tests verify that handler functions don't panic when given
|
|
// ActivityPub activities with nil or missing properties that could
|
|
// cause nil pointer dereferences.
|
|
|
|
func TestHandleFollowWithNilObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsFollow()
|
|
// Don't set object or actor - they will be nil
|
|
|
|
// This should return an error, not panic
|
|
err := handleFollowInboxRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleFollowInboxRequest with nil object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleFollowWithEmptyObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsFollow()
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
activity.SetActivityStreamsObject(object)
|
|
// Object is set but empty (no items)
|
|
|
|
err := handleFollowInboxRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleFollowInboxRequest with empty object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleFollowWithNilActorIRI(t *testing.T) {
|
|
activity := streams.NewActivityStreamsFollow()
|
|
|
|
// Set a valid object with IRI
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
objectNote := streams.NewActivityStreamsNote()
|
|
objectID := streams.NewJSONLDIdProperty()
|
|
objectID.SetIRI(mustParseURL("https://example.com/note/1"))
|
|
objectNote.SetJSONLDId(objectID)
|
|
object.AppendActivityStreamsNote(objectNote)
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
// Actor is nil
|
|
err := handleFollowInboxRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleFollowInboxRequest with nil actor should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleAnnounceWithNilObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsAnnounce()
|
|
// Don't set object or actor
|
|
|
|
err := handleAnnounceRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleAnnounceRequest with nil object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleAnnounceWithEmptyObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsAnnounce()
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
err := handleAnnounceRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleAnnounceRequest with empty object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleAnnounceWithNilActorIRI(t *testing.T) {
|
|
activity := streams.NewActivityStreamsAnnounce()
|
|
|
|
// Set object with IRI
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
object.AppendIRI(mustParseURL("https://example.com/note/1"))
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
// Actor is nil
|
|
err := handleAnnounceRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleAnnounceRequest with nil actor should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleLikeWithNilObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsLike()
|
|
// Don't set object or actor
|
|
|
|
err := handleLikeRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleLikeRequest with nil object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleLikeWithEmptyObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsLike()
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
err := handleLikeRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleLikeRequest with empty object should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleLikeWithNilActorIRI(t *testing.T) {
|
|
activity := streams.NewActivityStreamsLike()
|
|
|
|
// Set object with IRI
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
object.AppendIRI(mustParseURL("https://example.com/note/1"))
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
// Actor is nil
|
|
err := handleLikeRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleLikeRequest with nil actor should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleCreateWithNilId(t *testing.T) {
|
|
activity := streams.NewActivityStreamsCreate()
|
|
// Don't set JSONLD ID
|
|
|
|
err := handleCreateRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleCreateRequest with nil ID should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleCreateWithIdButNilIRI(t *testing.T) {
|
|
activity := streams.NewActivityStreamsCreate()
|
|
id := streams.NewJSONLDIdProperty()
|
|
// Set the ID property but don't set an IRI on it
|
|
activity.SetJSONLDId(id)
|
|
|
|
err := handleCreateRequest(context.Background(), activity)
|
|
if err == nil {
|
|
t.Error("handleCreateRequest with ID but nil IRI should return error")
|
|
}
|
|
}
|
|
|
|
func TestHandleUpdateWithNilObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsUpdate()
|
|
// Don't set object - should return nil (not an error, just skip)
|
|
|
|
// This should not panic and should return nil since we only care about Person updates
|
|
err := handleUpdateRequest(context.Background(), activity)
|
|
if err != nil {
|
|
t.Errorf("handleUpdateRequest with nil object should return nil (skip), got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHandleUpdateWithEmptyObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsUpdate()
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
// Should return nil since empty object means it's not a Person update
|
|
err := handleUpdateRequest(context.Background(), activity)
|
|
if err != nil {
|
|
t.Errorf("handleUpdateRequest with empty object should return nil (skip), got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestHandleUpdateWithNonPersonObject(t *testing.T) {
|
|
activity := streams.NewActivityStreamsUpdate()
|
|
object := streams.NewActivityStreamsObjectProperty()
|
|
note := streams.NewActivityStreamsNote()
|
|
object.AppendActivityStreamsNote(note)
|
|
activity.SetActivityStreamsObject(object)
|
|
|
|
// Should return nil since it's not a Person update
|
|
err := handleUpdateRequest(context.Background(), activity)
|
|
if err != nil {
|
|
t.Errorf("handleUpdateRequest with non-Person object should return nil (skip), got %v", err)
|
|
}
|
|
}
|
|
|
|
// TestNilSafetyNoPanic verifies that none of the handlers panic when given
|
|
// completely empty activities. This is the most important test - we want to
|
|
// ensure that malformed ActivityPub payloads don't crash the server.
|
|
func TestNilSafetyNoPanic(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
t.Run("Follow with nil properties", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleFollowInboxRequest panicked: %v", r)
|
|
}
|
|
}()
|
|
activity := streams.NewActivityStreamsFollow()
|
|
_ = handleFollowInboxRequest(ctx, activity)
|
|
})
|
|
|
|
t.Run("Announce with nil properties", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleAnnounceRequest panicked: %v", r)
|
|
}
|
|
}()
|
|
activity := streams.NewActivityStreamsAnnounce()
|
|
_ = handleAnnounceRequest(ctx, activity)
|
|
})
|
|
|
|
t.Run("Like with nil properties", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleLikeRequest panicked: %v", r)
|
|
}
|
|
}()
|
|
activity := streams.NewActivityStreamsLike()
|
|
_ = handleLikeRequest(ctx, activity)
|
|
})
|
|
|
|
t.Run("Create with nil properties", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleCreateRequest panicked: %v", r)
|
|
}
|
|
}()
|
|
activity := streams.NewActivityStreamsCreate()
|
|
_ = handleCreateRequest(ctx, activity)
|
|
})
|
|
|
|
t.Run("Update with nil properties", func(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
t.Errorf("handleUpdateRequest panicked: %v", r)
|
|
}
|
|
}()
|
|
activity := streams.NewActivityStreamsUpdate()
|
|
_ = handleUpdateRequest(ctx, activity)
|
|
})
|
|
}
|