package pub

import (
	"context"
	"net/url"
	"testing"

	"github.com/go-fed/activity/streams"
	"github.com/go-fed/activity/streams/vocab"
	"github.com/golang/mock/gomock"
)

// TestFederatedCallbacks tests the overriding functionality.
func TestFederatedCallbacks(t *testing.T) {
	t.Run("ReturnsOtherCallback", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsListen) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsListen) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find extra function")
		}

	})
	t.Run("OverridesCreate", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsCreate) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsCreate) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesUpdate", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsUpdate) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsUpdate) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesDelete", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsDelete) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsDelete) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesFollow", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsFollow) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsFollow) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesAccept", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsAccept) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsAccept) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesReject", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsReject) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsReject) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesAdd", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsAdd) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsAdd) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesRemove", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsRemove) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsRemove) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesLike", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsLike) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsLike) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesAnnounce", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsAnnounce) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsAnnounce) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesUndo", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsUndo) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsUndo) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
	t.Run("OverridesBlock", func(t *testing.T) {
		ok := false
		o := func(context.Context, vocab.ActivityStreamsBlock) error {
			ok = true
			return nil
		}
		var w FederatingWrappedCallbacks
		for _, f := range w.callbacks([]interface{}{o}) {
			if fn, ok := f.(func(context.Context, vocab.ActivityStreamsBlock) error); ok {
				fn(nil, nil)
			}
		}
		if !ok {
			t.Fatalf("could not find overridden function")
		}
	})
}

func TestFederatedCreate(t *testing.T) {
	newCreateFn := func() vocab.ActivityStreamsCreate {
		c := streams.NewActivityStreamsCreate()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		c.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		c.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		c.SetActivityStreamsObject(op)
		return c
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase, mockTp *MockTransport) {
		mockDB = NewMockDatabase(ctl)
		mockTp = NewMockTransport(ctl)
		w.db = mockDB
		w.newTransport = func(c context.Context, a *url.URL, s string) (Transport, error) {
			return mockTp, nil
		}
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		c := newCreateFn()
		c.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.create(ctx, c)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		c := newCreateFn()
		c.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.create(ctx, c)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("CreatesFederatedObject", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Create(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		c := newCreateFn()
		err := w.create(ctx, c)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CreatesAllFederatedObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Create(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId2))
		mockDB.EXPECT().Create(ctx, testFederatedNote2)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId2))
		c := newCreateFn()
		c.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.create(ctx, c)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("DereferencesIRIObject", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, mockTp := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Create(ctx, toDeserializedForm(testFederatedNote))
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		mockTp.EXPECT().Dereference(ctx, mustParse(testNoteId1)).Return(
			mustSerializeToBytes(testFederatedNote), nil)
		c := newCreateFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testNoteId1))
		c.SetActivityStreamsObject(op)
		err := w.create(ctx, c)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Create(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		c := newCreateFn()
		var gotc context.Context
		var got vocab.ActivityStreamsCreate
		w.Create = func(ctx context.Context, v vocab.ActivityStreamsCreate) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.create(ctx, c)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, c, got)
	})
}

func TestFederatedUpdate(t *testing.T) {
	newUpdateFn := func() vocab.ActivityStreamsUpdate {
		u := streams.NewActivityStreamsUpdate()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testNewActivityIRI))
		u.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		u.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		u.SetActivityStreamsObject(op)
		return u
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		u := newUpdateFn()
		u.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.update(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		u := newUpdateFn()
		u.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.update(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfOriginMismatchesObject", func(t *testing.T) {
		u := newUpdateFn()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		u.SetJSONLDId(id)
		var w FederatingWrappedCallbacks
		err := w.update(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("UpdatesFederatedObject", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Update(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		u := newUpdateFn()
		err := w.update(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("UpdatesAllFederatedObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Update(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId2))
		mockDB.EXPECT().Update(ctx, testFederatedNote2)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId2))
		u := newUpdateFn()
		u.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.update(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("ErrorIfObjectIsIRI", func(t *testing.T) {
		u := newUpdateFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testNoteId1))
		u.SetActivityStreamsObject(op)
		var w FederatingWrappedCallbacks
		err := w.update(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Update(ctx, testFederatedNote)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		u := newUpdateFn()
		var gotc context.Context
		var got vocab.ActivityStreamsUpdate
		w.Update = func(ctx context.Context, v vocab.ActivityStreamsUpdate) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.update(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, u, got)
	})
}

func TestFederatedDelete(t *testing.T) {
	newDeleteFn := func() vocab.ActivityStreamsDelete {
		d := streams.NewActivityStreamsDelete()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testNewActivityIRI))
		d.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		d.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testNoteId1))
		d.SetActivityStreamsObject(op)
		return d
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		d := newDeleteFn()
		d.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.deleteFn(ctx, d)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		d := newDeleteFn()
		d.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.deleteFn(ctx, d)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfOriginMismatchesObject", func(t *testing.T) {
		d := newDeleteFn()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		d.SetJSONLDId(id)
		var w FederatingWrappedCallbacks
		err := w.deleteFn(ctx, d)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("DeletesFederatedObject", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Delete(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		d := newDeleteFn()
		err := w.deleteFn(ctx, d)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("DeletesAllFederatedObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Delete(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId2))
		mockDB.EXPECT().Delete(ctx, mustParse(testNoteId2))
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId2))
		d := newDeleteFn()
		d.GetActivityStreamsObject().AppendIRI(mustParse(testNoteId2))
		err := w.deleteFn(ctx, d)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Delete(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		d := newDeleteFn()
		var gotc context.Context
		var got vocab.ActivityStreamsDelete
		w.Delete = func(ctx context.Context, v vocab.ActivityStreamsDelete) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.deleteFn(ctx, d)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, d, got)
	})
}

func TestFederatedFollow(t *testing.T) {
	newFollowFn := func() vocab.ActivityStreamsFollow {
		f := streams.NewActivityStreamsFollow()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testNewActivityIRI))
		f.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		f.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testFederatedActorIRI2))
		f.SetActivityStreamsObject(op)
		return f
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		w.inboxIRI = mustParse(testMyInboxIRI)
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		f := newFollowFn()
		f.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.follow(ctx, f)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		f := newFollowFn()
		f.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.follow(ctx, f)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("OnFollowNothingDoesNothing", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		w.OnFollow = OnFollowDoNothing
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		f := newFollowFn()
		err := w.follow(ctx, f)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("OnFollowAutomaticallyAcceptUpdatesFollowers", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		w.OnFollow = OnFollowAutomaticallyAccept
		w.addNewIds = func(c context.Context, activity Activity) error {
			return nil
		}
		w.deliver = func(c context.Context, outboxIRI *url.URL, activity Activity) error {
			return nil
		}
		followers := streams.NewActivityStreamsCollection()
		expectFollowers := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActorIRI))
		expectFollowers.SetActivityStreamsItems(expectItems)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Followers(ctx, mustParse(testFederatedActorIRI2)).Return(
			followers, nil)
		mockDB.EXPECT().Update(ctx, expectFollowers)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().OutboxForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testMyOutboxIRI), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		f := newFollowFn()
		err := w.follow(ctx, f)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("OnFollowAutomaticallyAcceptDelivers", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		w.OnFollow = OnFollowAutomaticallyAccept
		w.addNewIds = func(c context.Context, activity Activity) error {
			return nil
		}
		w.deliver = func(c context.Context, outboxIRI *url.URL, activity Activity) error {
			if !streams.IsOrExtendsActivityStreamsAccept(activity) {
				t.Fatalf("expected Accept, got %T", activity)
			}
			return nil
		}
		followers := streams.NewActivityStreamsCollection()
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Followers(ctx, mustParse(testFederatedActorIRI2)).Return(
			followers, nil)
		mockDB.EXPECT().Update(ctx, gomock.Any())
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().OutboxForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testMyOutboxIRI), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		f := newFollowFn()
		err := w.follow(ctx, f)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("OnFollowAutomaticallyRejectDelivers", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		w.OnFollow = OnFollowAutomaticallyReject
		w.addNewIds = func(c context.Context, activity Activity) error {
			return nil
		}
		w.deliver = func(c context.Context, outboxIRI *url.URL, activity Activity) error {
			if !streams.IsOrExtendsActivityStreamsReject(activity) {
				t.Fatalf("expected Reject, got %T", activity)
			}
			return nil
		}
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().OutboxForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testMyOutboxIRI), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		f := newFollowFn()
		err := w.follow(ctx, f)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		w.OnFollow = OnFollowDoNothing
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		f := newFollowFn()
		var gotc context.Context
		var got vocab.ActivityStreamsFollow
		w.Follow = func(ctx context.Context, v vocab.ActivityStreamsFollow) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.follow(ctx, f)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, f, got)
	})
}

func TestFederatedAccept(t *testing.T) {
	newAcceptFn := func() vocab.ActivityStreamsAccept {
		c := streams.NewActivityStreamsAccept()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI2))
		c.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		c.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsFollow(testFollow)
		c.SetActivityStreamsObject(op)
		return c
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase, mockTp *MockTransport) {
		mockDB = NewMockDatabase(ctl)
		mockTp = NewMockTransport(ctl)
		w.inboxIRI = mustParse(testMyInboxIRI)
		w.db = mockDB
		w.newTransport = func(c context.Context, a *url.URL, s string) (Transport, error) {
			return mockTp, nil
		}
		return
	}
	t.Run("DoesNothingIfNoObjects", func(t *testing.T) {
		a := newAcceptFn()
		a.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("DereferencesObjectIRI", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, mockTp := setupFn(ctl)
		followers := streams.NewActivityStreamsCollection()
		expectFollowers := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActorIRI))
		expectFollowers.SetActivityStreamsItems(expectItems)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testFollow), nil)
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI))
		mockDB.EXPECT().Get(ctx, mustParse(testFederatedActivityIRI)).Return(
			testFollow, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Following(ctx, mustParse(testFederatedActorIRI2)).Return(
			followers, nil)
		mockDB.EXPECT().Update(ctx, expectFollowers)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActorIRI2))
		a := newAcceptFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testFederatedActivityIRI))
		a.SetActivityStreamsObject(op)
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("IgnoresNonFollowObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		a := newAcceptFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsListen(testListen)
		a.SetActivityStreamsObject(op)
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("IgnoresFollowObjectsNotContainingMe", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI3), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		a := newAcceptFn()
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("ErrorIfPeerLiedAboutOurFollowId", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI))
		mockDB.EXPECT().Get(ctx, mustParse(testFederatedActivityIRI)).Return(
			testListen, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI))
		a := newAcceptFn()
		err := w.accept(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("UpdatesFollowingCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB, _ := setupFn(ctl)
		followers := streams.NewActivityStreamsCollection()
		expectFollowers := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActorIRI))
		expectFollowers.SetActivityStreamsItems(expectItems)
		mockDB.EXPECT().Lock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().ActorForInbox(ctx, mustParse(testMyInboxIRI)).Return(
			mustParse(testFederatedActorIRI2), nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testMyInboxIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI))
		mockDB.EXPECT().Get(ctx, mustParse(testFederatedActivityIRI)).Return(
			testFollow, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testFederatedActorIRI2))
		mockDB.EXPECT().Following(ctx, mustParse(testFederatedActorIRI2)).Return(
			followers, nil)
		mockDB.EXPECT().Update(ctx, expectFollowers)
		mockDB.EXPECT().Unlock(ctx, mustParse(testFederatedActorIRI2))
		a := newAcceptFn()
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		a := newAcceptFn()
		a.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		var gotc context.Context
		var got vocab.ActivityStreamsAccept
		w.Accept = func(ctx context.Context, v vocab.ActivityStreamsAccept) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.accept(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, a, got)
	})
}

func TestFederatedReject(t *testing.T) {
	ctx := context.Background()
	t.Run("CallsCustomCallback", func(t *testing.T) {
		r := streams.NewActivityStreamsReject()
		var w FederatingWrappedCallbacks
		var gotc context.Context
		var got vocab.ActivityStreamsReject
		w.Reject = func(ctx context.Context, v vocab.ActivityStreamsReject) error {
			gotc = ctx
			got = v
			return nil
		}
		err := w.reject(ctx, r)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, r, got)
	})
}

func TestFederatedAdd(t *testing.T) {
	newAddFn := func() vocab.ActivityStreamsAdd {
		a := streams.NewActivityStreamsAdd()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		a.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		a.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		a.SetActivityStreamsObject(op)
		tp := streams.NewActivityStreamsTargetProperty()
		tp.AppendIRI(mustParse(testAudienceIRI))
		a.SetActivityStreamsTarget(tp)
		return a
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		a := newAddFn()
		a.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.add(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		a := newAddFn()
		a.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.add(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfNoTarget", func(t *testing.T) {
		a := newAddFn()
		a.SetActivityStreamsTarget(nil)
		var w FederatingWrappedCallbacks
		err := w.add(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfTargetLengthZero", func(t *testing.T) {
		a := newAddFn()
		a.GetActivityStreamsTarget().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.add(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("AddsAllObjectIdsToCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		expectCol1 := streams.NewActivityStreamsCollection()
		items1 := streams.NewActivityStreamsItemsProperty()
		items1.AppendIRI(mustParse(testNoteId1))
		items1.AppendIRI(mustParse(testNoteId2))
		expectCol1.SetActivityStreamsItems(items1)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		a := newAddFn()
		a.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.add(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsAllObjectIdsToOrderedCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsOrderedCollection()
		expectCol1 := streams.NewActivityStreamsOrderedCollection()
		items1 := streams.NewActivityStreamsOrderedItemsProperty()
		items1.AppendIRI(mustParse(testNoteId1))
		items1.AppendIRI(mustParse(testNoteId2))
		expectCol1.SetActivityStreamsOrderedItems(items1)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		a := newAddFn()
		a.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.add(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsAllObjectIdsToEachTarget", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		expectCol1 := streams.NewActivityStreamsCollection()
		items1 := streams.NewActivityStreamsItemsProperty()
		items1.AppendIRI(mustParse(testNoteId1))
		expectCol1.SetActivityStreamsItems(items1)
		col2 := streams.NewActivityStreamsCollection()
		expectCol2 := streams.NewActivityStreamsCollection()
		items2 := streams.NewActivityStreamsItemsProperty()
		items2.AppendIRI(mustParse(testNoteId1))
		expectCol2.SetActivityStreamsItems(items2)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI2))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI2)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI2)).Return(
			col2, nil)
		mockDB.EXPECT().Update(ctx, expectCol2).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2))
		a := newAddFn()
		a.GetActivityStreamsTarget().AppendIRI(mustParse(testAudienceIRI2))
		err := w.add(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("ReturnsErrorIfTargetIsNotCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		notCol := streams.NewActivityStreamsNote()
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			notCol, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		a := newAddFn()
		err := w.add(ctx, a)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, gomock.Any()).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		var gotc context.Context
		var got vocab.ActivityStreamsAdd
		w.Add = func(ctx context.Context, v vocab.ActivityStreamsAdd) error {
			gotc = ctx
			got = v
			return nil
		}
		a := newAddFn()
		err := w.add(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, a, got)
	})
}

func TestFederatedRemove(t *testing.T) {
	newRemoveFn := func() vocab.ActivityStreamsRemove {
		r := streams.NewActivityStreamsRemove()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		r.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		r.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		r.SetActivityStreamsObject(op)
		tp := streams.NewActivityStreamsTargetProperty()
		tp.AppendIRI(mustParse(testAudienceIRI))
		r.SetActivityStreamsTarget(tp)
		return r
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		r := newRemoveFn()
		r.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.remove(ctx, r)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		r := newRemoveFn()
		r.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.remove(ctx, r)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfNoTarget", func(t *testing.T) {
		r := newRemoveFn()
		r.SetActivityStreamsTarget(nil)
		var w FederatingWrappedCallbacks
		err := w.remove(ctx, r)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfTargetLengthZero", func(t *testing.T) {
		r := newRemoveFn()
		r.GetActivityStreamsTarget().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.remove(ctx, r)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("RemovesAllObjectIdsFromCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		items := streams.NewActivityStreamsItemsProperty()
		items.AppendIRI(mustParse(testAudienceIRI))
		items.AppendIRI(mustParse(testAudienceIRI2))
		items.AppendIRI(mustParse(testNoteId1))
		items.AppendIRI(mustParse(testNoteId2))
		col1.SetActivityStreamsItems(items)
		expectCol1 := streams.NewActivityStreamsCollection()
		items1 := streams.NewActivityStreamsItemsProperty()
		items1.AppendIRI(mustParse(testAudienceIRI))
		items1.AppendIRI(mustParse(testAudienceIRI2))
		expectCol1.SetActivityStreamsItems(items1)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		r := newRemoveFn()
		r.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.remove(ctx, r)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("RemovesAllObjectIdsFromOrderedCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsOrderedCollection()
		items := streams.NewActivityStreamsOrderedItemsProperty()
		items.AppendIRI(mustParse(testAudienceIRI))
		items.AppendIRI(mustParse(testAudienceIRI2))
		items.AppendIRI(mustParse(testNoteId1))
		items.AppendIRI(mustParse(testNoteId2))
		col1.SetActivityStreamsOrderedItems(items)
		expectCol1 := streams.NewActivityStreamsOrderedCollection()
		items1 := streams.NewActivityStreamsOrderedItemsProperty()
		items1.AppendIRI(mustParse(testAudienceIRI))
		items1.AppendIRI(mustParse(testAudienceIRI2))
		expectCol1.SetActivityStreamsOrderedItems(items1)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		r := newRemoveFn()
		r.GetActivityStreamsObject().AppendActivityStreamsNote(testFederatedNote2)
		err := w.remove(ctx, r)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("RemovesAllObjectIdsFromEachTarget", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		items := streams.NewActivityStreamsItemsProperty()
		items.AppendIRI(mustParse(testFederatedActorIRI3))
		items.AppendIRI(mustParse(testFederatedActorIRI4))
		items.AppendIRI(mustParse(testNoteId1))
		col1.SetActivityStreamsItems(items)
		expectCol1 := streams.NewActivityStreamsCollection()
		items1 := streams.NewActivityStreamsItemsProperty()
		items1.AppendIRI(mustParse(testFederatedActorIRI3))
		items1.AppendIRI(mustParse(testFederatedActorIRI4))
		expectCol1.SetActivityStreamsItems(items1)
		col2 := streams.NewActivityStreamsCollection()
		items0 := streams.NewActivityStreamsItemsProperty()
		items0.AppendIRI(mustParse(testFederatedActorIRI))
		items0.AppendIRI(mustParse(testNoteId1))
		items0.AppendIRI(mustParse(testFederatedActorIRI2))
		col2.SetActivityStreamsItems(items0)
		expectCol2 := streams.NewActivityStreamsCollection()
		items2 := streams.NewActivityStreamsItemsProperty()
		items2.AppendIRI(mustParse(testFederatedActorIRI))
		items2.AppendIRI(mustParse(testFederatedActorIRI2))
		expectCol2.SetActivityStreamsItems(items2)
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, expectCol1).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI2))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI2)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI2)).Return(
			col2, nil)
		mockDB.EXPECT().Update(ctx, expectCol2).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2))
		r := newRemoveFn()
		r.GetActivityStreamsTarget().AppendIRI(mustParse(testAudienceIRI2))
		err := w.remove(ctx, r)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("ReturnsErrorIfTargetIsNotCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		notCol := streams.NewActivityStreamsNote()
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			notCol, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		r := newRemoveFn()
		err := w.remove(ctx, r)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		col1 := streams.NewActivityStreamsCollection()
		mockDB.EXPECT().Lock(ctx, mustParse(testAudienceIRI))
		mockDB.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(
			col1, nil)
		mockDB.EXPECT().Update(ctx, gomock.Any()).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testAudienceIRI))
		var gotc context.Context
		var got vocab.ActivityStreamsRemove
		w.Remove = func(ctx context.Context, v vocab.ActivityStreamsRemove) error {
			gotc = ctx
			got = v
			return nil
		}
		r := newRemoveFn()
		err := w.remove(ctx, r)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, r, got)
	})
}

func TestFederatedLike(t *testing.T) {
	newLikeFn := func() vocab.ActivityStreamsLike {
		l := streams.NewActivityStreamsLike()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		l.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		l.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		l.SetActivityStreamsObject(op)
		return l
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		l := newLikeFn()
		l.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.like(ctx, l)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		l := newLikeFn()
		l.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.like(ctx, l)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("SkipsUnownedObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(false, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		l := newLikeFn()
		err := w.like(ctx, l)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToNewLikesCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		expectNote := streams.NewActivityStreamsNote()
		expectLikes := streams.NewActivityStreamsLikesProperty()
		expectCol := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectCol.SetActivityStreamsItems(expectItems)
		expectLikes.SetActivityStreamsCollection(expectCol)
		expectNote.SetActivityStreamsLikes(expectLikes)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		l := newLikeFn()
		err := w.like(ctx, l)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToExistingLikesCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		likes := streams.NewActivityStreamsLikesProperty()
		col := streams.NewActivityStreamsCollection()
		items := streams.NewActivityStreamsItemsProperty()
		items.AppendIRI(mustParse(testFederatedActivityIRI2))
		col.SetActivityStreamsItems(items)
		likes.SetActivityStreamsCollection(col)
		note.SetActivityStreamsLikes(likes)
		expectNote := streams.NewActivityStreamsNote()
		expectLikes := streams.NewActivityStreamsLikesProperty()
		expectCol := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI2))
		expectCol.SetActivityStreamsItems(expectItems)
		expectLikes.SetActivityStreamsCollection(expectCol)
		expectNote.SetActivityStreamsLikes(expectLikes)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		l := newLikeFn()
		err := w.like(ctx, l)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToExistingLikesOrderedCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		likes := streams.NewActivityStreamsLikesProperty()
		col := streams.NewActivityStreamsOrderedCollection()
		items := streams.NewActivityStreamsOrderedItemsProperty()
		items.AppendIRI(mustParse(testFederatedActivityIRI2))
		col.SetActivityStreamsOrderedItems(items)
		likes.SetActivityStreamsOrderedCollection(col)
		note.SetActivityStreamsLikes(likes)
		expectNote := streams.NewActivityStreamsNote()
		expectLikes := streams.NewActivityStreamsLikesProperty()
		expectCol := streams.NewActivityStreamsOrderedCollection()
		expectItems := streams.NewActivityStreamsOrderedItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI2))
		expectCol.SetActivityStreamsOrderedItems(expectItems)
		expectLikes.SetActivityStreamsOrderedCollection(expectCol)
		expectNote.SetActivityStreamsLikes(expectLikes)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		l := newLikeFn()
		err := w.like(ctx, l)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		expectNote := streams.NewActivityStreamsNote()
		expectLikes := streams.NewActivityStreamsLikesProperty()
		expectCol := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectCol.SetActivityStreamsItems(expectItems)
		expectLikes.SetActivityStreamsCollection(expectCol)
		expectNote.SetActivityStreamsLikes(expectLikes)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		var gotc context.Context
		var got vocab.ActivityStreamsLike
		w.Like = func(ctx context.Context, v vocab.ActivityStreamsLike) error {
			gotc = ctx
			got = v
			return nil
		}
		l := newLikeFn()
		err := w.like(ctx, l)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, l, got)
	})
}

func TestFederatedAnnounce(t *testing.T) {
	newAnnounceFn := func() vocab.ActivityStreamsAnnounce {
		a := streams.NewActivityStreamsAnnounce()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		a.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		a.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsNote(testFederatedNote)
		a.SetActivityStreamsObject(op)
		return a
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockDB *MockDatabase) {
		mockDB = NewMockDatabase(ctl)
		w.db = mockDB
		return
	}
	t.Run("DoesNothingWhenNoObjects", func(t *testing.T) {
		a := newAnnounceFn()
		a.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("SkipsUnownedObjects", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(
			false, nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		a := newAnnounceFn()
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToNewSharesCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		expectNote := streams.NewActivityStreamsNote()
		expectShares := streams.NewActivityStreamsSharesProperty()
		expectCol := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectCol.SetActivityStreamsItems(expectItems)
		expectShares.SetActivityStreamsCollection(expectCol)
		expectNote.SetActivityStreamsShares(expectShares)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		a := newAnnounceFn()
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToExistingSharesCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		shares := streams.NewActivityStreamsSharesProperty()
		col := streams.NewActivityStreamsCollection()
		items := streams.NewActivityStreamsItemsProperty()
		items.AppendIRI(mustParse(testFederatedActivityIRI2))
		col.SetActivityStreamsItems(items)
		shares.SetActivityStreamsCollection(col)
		note.SetActivityStreamsShares(shares)
		expectNote := streams.NewActivityStreamsNote()
		expectShares := streams.NewActivityStreamsSharesProperty()
		expectCol := streams.NewActivityStreamsCollection()
		expectItems := streams.NewActivityStreamsItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI2))
		expectCol.SetActivityStreamsItems(expectItems)
		expectShares.SetActivityStreamsCollection(expectCol)
		expectNote.SetActivityStreamsShares(expectShares)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		a := newAnnounceFn()
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("AddsToExistingSharesOrderedCollection", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		shares := streams.NewActivityStreamsSharesProperty()
		col := streams.NewActivityStreamsOrderedCollection()
		items := streams.NewActivityStreamsOrderedItemsProperty()
		items.AppendIRI(mustParse(testFederatedActivityIRI2))
		col.SetActivityStreamsOrderedItems(items)
		shares.SetActivityStreamsOrderedCollection(col)
		note.SetActivityStreamsShares(shares)
		expectNote := streams.NewActivityStreamsNote()
		expectShares := streams.NewActivityStreamsSharesProperty()
		expectCol := streams.NewActivityStreamsOrderedCollection()
		expectItems := streams.NewActivityStreamsOrderedItemsProperty()
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI))
		expectItems.AppendIRI(mustParse(testFederatedActivityIRI2))
		expectCol.SetActivityStreamsOrderedItems(expectItems)
		expectShares.SetActivityStreamsOrderedCollection(expectCol)
		expectNote.SetActivityStreamsShares(expectShares)
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, expectNote).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		a := newAnnounceFn()
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockDB := setupFn(ctl)
		note := streams.NewActivityStreamsNote()
		mockDB.EXPECT().Lock(ctx, mustParse(testNoteId1))
		mockDB.EXPECT().Owns(ctx, mustParse(testNoteId1)).Return(
			true, nil)
		mockDB.EXPECT().Get(ctx, mustParse(testNoteId1)).Return(
			note, nil)
		mockDB.EXPECT().Update(ctx, gomock.Any()).Return(nil)
		mockDB.EXPECT().Unlock(ctx, mustParse(testNoteId1))
		var gotc context.Context
		var got vocab.ActivityStreamsAnnounce
		w.Announce = func(ctx context.Context, v vocab.ActivityStreamsAnnounce) error {
			gotc = ctx
			got = v
			return nil
		}
		a := newAnnounceFn()
		err := w.announce(ctx, a)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, a, got)
	})
}

func TestFederatedUndo(t *testing.T) {
	newUndoFn := func() vocab.ActivityStreamsUndo {
		u := streams.NewActivityStreamsUndo()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI2))
		u.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		u.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendActivityStreamsListen(testListen)
		u.SetActivityStreamsObject(op)
		return u
	}
	ctx := context.Background()
	setupFn := func(ctl *gomock.Controller) (w FederatingWrappedCallbacks, mockTp *MockTransport) {
		mockTp = NewMockTransport(ctl)
		w.inboxIRI = mustParse(testMyInboxIRI)
		w.newTransport = func(c context.Context, a *url.URL, s string) (Transport, error) {
			return mockTp, nil
		}
		return
	}
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		u := newUndoFn()
		u.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.undo(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		u := newUndoFn()
		u.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.undo(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfActorMismatch", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockTp := setupFn(ctl)
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testListen), nil)
		u := newUndoFn()
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI2))
		u.SetActivityStreamsActor(actor)
		err := w.undo(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfActorMismatchWhenDereferencingIRI", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockTp := setupFn(ctl)
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testFollow), nil)
		u := newUndoFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testFederatedActivityIRI))
		u.SetActivityStreamsObject(op)
		err := w.undo(ctx, u)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("DereferencesWhenUndoValue", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockTp := setupFn(ctl)
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testListen), nil)
		u := newUndoFn()
		err := w.undo(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("DereferencesWhenUndoIRI", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockTp := setupFn(ctl)
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testListen), nil)
		u := newUndoFn()
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testFederatedActivityIRI))
		u.SetActivityStreamsObject(op)
		err := w.undo(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		ctl := gomock.NewController(t)
		defer ctl.Finish()
		w, mockTp := setupFn(ctl)
		mockTp.EXPECT().Dereference(ctx, mustParse(testFederatedActivityIRI)).Return(
			mustSerializeToBytes(testListen), nil)
		var gotc context.Context
		var got vocab.ActivityStreamsUndo
		w.Undo = func(ctx context.Context, v vocab.ActivityStreamsUndo) error {
			gotc = ctx
			got = v
			return nil
		}
		u := newUndoFn()
		err := w.undo(ctx, u)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, u, got)
	})
}

func TestFederatedBlock(t *testing.T) {
	newBlockFn := func() vocab.ActivityStreamsBlock {
		b := streams.NewActivityStreamsBlock()
		id := streams.NewJSONLDIdProperty()
		id.Set(mustParse(testFederatedActivityIRI))
		b.SetJSONLDId(id)
		actor := streams.NewActivityStreamsActorProperty()
		actor.AppendIRI(mustParse(testFederatedActorIRI))
		b.SetActivityStreamsActor(actor)
		op := streams.NewActivityStreamsObjectProperty()
		op.AppendIRI(mustParse(testFederatedActorIRI2))
		b.SetActivityStreamsObject(op)
		return b
	}
	ctx := context.Background()
	t.Run("ErrorIfNoObject", func(t *testing.T) {
		b := newBlockFn()
		b.SetActivityStreamsObject(nil)
		var w FederatingWrappedCallbacks
		err := w.block(ctx, b)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("ErrorIfObjectLengthZero", func(t *testing.T) {
		b := newBlockFn()
		b.GetActivityStreamsObject().Remove(0)
		var w FederatingWrappedCallbacks
		err := w.block(ctx, b)
		if err == nil {
			t.Fatalf("expected error, got none")
		}
	})
	t.Run("CallsCustomCallback", func(t *testing.T) {
		var w FederatingWrappedCallbacks
		var gotc context.Context
		var got vocab.ActivityStreamsBlock
		w.Block = func(ctx context.Context, v vocab.ActivityStreamsBlock) error {
			gotc = ctx
			got = v
			return nil
		}
		b := newBlockFn()
		err := w.block(ctx, b)
		if err != nil {
			t.Fatalf("got error %s", err)
		}
		assertEqual(t, ctx, gotc)
		assertEqual(t, b, got)
	})
}
