Increase side effect actor test coverage.

Introduce DefaultCallback to the protocol interfaces.
このコミットが含まれているのは:
Cory Slep 2019-02-20 21:31:11 +01:00
コミット 36a38b74b0
8個のファイルの変更286行の追加32行の削除

ファイルの表示

@ -64,7 +64,18 @@ type FederatingProtocol interface {
// To override the default behavior, instead supply the function in
// 'other', which does not guarantee the application will be compliant
// with the ActivityPub Social Protocol.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension. The unhandled ones are passed to DefaultCallback.
Callbacks(c context.Context) (wrapped FederatingWrappedCallbacks, other []interface{})
// DefaultCallback is called for types that go-fed can deserialize but
// are not handled by the application's callbacks returned in the
// Callbacks method.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension, so the unhandled ones are passed to
// DefaultCallback.
DefaultCallback(c context.Context, activity Activity) error
// MaxInboxForwardingRecursionDepth determines how deep to search within
// an activity to determine if inbox forwarding needs to occur.
//

ファイルの表示

@ -250,7 +250,7 @@ func (w FederatingWrappedCallbacks) create(c context.Context, a vocab.ActivitySt
if err != nil {
return err
}
} else {
} else if t == nil {
return fmt.Errorf("cannot handle federated create: object is neither a value nor IRI")
}
id, err := GetId(t)
@ -509,7 +509,7 @@ func (w FederatingWrappedCallbacks) accept(c context.Context, a vocab.ActivitySt
if err != nil {
return err
}
} else {
} else if t == nil {
return fmt.Errorf("cannot handle federated create: object is neither a value nor IRI")
}
// Ensure it is a Follow.

ファイルの表示

@ -81,6 +81,20 @@ func (mr *MockFederatingProtocolMockRecorder) Callbacks(c interface{}) *gomock.C
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Callbacks", reflect.TypeOf((*MockFederatingProtocol)(nil).Callbacks), c)
}
// DefaultCallback mocks base method
func (m *MockFederatingProtocol) DefaultCallback(c context.Context, activity Activity) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DefaultCallback", c, activity)
ret0, _ := ret[0].(error)
return ret0
}
// DefaultCallback indicates an expected call of DefaultCallback
func (mr *MockFederatingProtocolMockRecorder) DefaultCallback(c, activity interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultCallback", reflect.TypeOf((*MockFederatingProtocol)(nil).DefaultCallback), c, activity)
}
// MaxInboxForwardingRecursionDepth mocks base method
func (m *MockFederatingProtocol) MaxInboxForwardingRecursionDepth(c context.Context) int {
m.ctrl.T.Helper()

ファイルの表示

@ -65,6 +65,20 @@ func (mr *MockSocialProtocolMockRecorder) Callbacks(c interface{}) *gomock.Call
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Callbacks", reflect.TypeOf((*MockSocialProtocol)(nil).Callbacks), c)
}
// DefaultCallback mocks base method
func (m *MockSocialProtocol) DefaultCallback(c context.Context, activity Activity) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DefaultCallback", c, activity)
ret0, _ := ret[0].(error)
return ret0
}
// DefaultCallback indicates an expected call of DefaultCallback
func (mr *MockSocialProtocolMockRecorder) DefaultCallback(c, activity interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DefaultCallback", reflect.TypeOf((*MockSocialProtocol)(nil).DefaultCallback), c, activity)
}
// GetOutbox mocks base method
func (m *MockSocialProtocol) GetOutbox(c context.Context, r *http.Request) (vocab.ActivityStreamsOrderedCollectionPage, error) {
m.ctrl.T.Helper()

ファイルの表示

@ -15,14 +15,15 @@ import (
)
const (
testMyInboxIRI = "https://example.com/addison/inbox"
testMyOutboxIRI = "https://example.com/addison/outbox"
testFederatedActivityIRI = "https://other.example.com/activity/1"
testFederatedActorIRI = "https://other.example.com/dakota"
testFederatedActorIRI2 = "https://other.example.com/addison"
testNoteId1 = "https://example.com/note/1"
testNoteId2 = "https://example.com/note/2"
testNewActivityIRI = "https://example.com/new/1"
testMyInboxIRI = "https://example.com/addison/inbox"
testMyOutboxIRI = "https://example.com/addison/outbox"
testFederatedActivityIRI = "https://other.example.com/activity/1"
testFederatedActivityIRI2 = "https://other.example.com/activity/2"
testFederatedActorIRI = "https://other.example.com/dakota"
testFederatedActorIRI2 = "https://other.example.com/addison"
testNoteId1 = "https://example.com/note/1"
testNoteId2 = "https://example.com/note/2"
testNewActivityIRI = "https://example.com/new/1"
)
// mustParse parses a URL or panics.
@ -79,6 +80,17 @@ var (
// testOrderedCollectionDedupedElemsString is the JSON-LD version of the
// testOrderedCollectionDedupedElems value with duplicates removed
testOrderedCollectionDedupedElemsString string
// testEmptyOrderedCollection is an empty OrderedCollectionPage.
testEmptyOrderedCollection vocab.ActivityStreamsOrderedCollectionPage
// testOrderedCollectionWithFederatedId has the federated Activity id.
testOrderedCollectionWithFederatedId vocab.ActivityStreamsOrderedCollectionPage
// testListen is a test Listen Activity.
testListen vocab.ActivityStreamsListen
// testOrderedCollectionWithFederatedId2 has the second federated
// Activity id.
testOrderedCollectionWithFederatedId2 vocab.ActivityStreamsOrderedCollectionPage
// testOrderedCollectionWithBothFederatedIds has both federated Activity id.
testOrderedCollectionWithBothFederatedIds vocab.ActivityStreamsOrderedCollectionPage
)
// The test data cannot be created at init time since that is when the hooks of
@ -94,6 +106,9 @@ func setupData() {
content := streams.NewActivityStreamsContentProperty()
content.AppendXMLSchemaString("This is a simple note being federated.")
testFederatedNote.SetActivityStreamsContent(content)
id := streams.NewActivityStreamsIdProperty()
id.Set(mustParse(testNoteId1))
testFederatedNote.SetActivityStreamsId(id)
}()
// testMyNote
func() {
@ -162,6 +177,45 @@ func setupData() {
testOrderedCollectionDupedElems.SetActivityStreamsOrderedItems(oi)
testOrderedCollectionDedupedElemsString = `{"@context":"https://www.w3.org/TR/activitystreams-vocabulary","orderedItems":"https://example.com/note/1","type":"OrderedCollectionPage"}`
}()
// testEmptyOrderedCollection
func() {
testEmptyOrderedCollection = streams.NewActivityStreamsOrderedCollectionPage()
}()
// testOrderedCollectionWithFederatedId
func() {
testOrderedCollectionWithFederatedId = streams.NewActivityStreamsOrderedCollectionPage()
oi := streams.NewActivityStreamsOrderedItemsProperty()
oi.AppendIRI(mustParse(testFederatedActivityIRI))
testOrderedCollectionWithFederatedId.SetActivityStreamsOrderedItems(oi)
}()
// testListen
func() {
testListen = streams.NewActivityStreamsListen()
id := streams.NewActivityStreamsIdProperty()
id.Set(mustParse(testFederatedActivityIRI))
testListen.SetActivityStreamsId(id)
actor := streams.NewActivityStreamsActorProperty()
actor.AppendIRI(mustParse(testFederatedActorIRI))
testListen.SetActivityStreamsActor(actor)
op := streams.NewActivityStreamsObjectProperty()
op.AppendActivityStreamsNote(testFederatedNote)
testListen.SetActivityStreamsObject(op)
}()
// testOrderedCollectionWithFederatedId2
func() {
testOrderedCollectionWithFederatedId2 = streams.NewActivityStreamsOrderedCollectionPage()
oi := streams.NewActivityStreamsOrderedItemsProperty()
oi.AppendIRI(mustParse(testFederatedActivityIRI2))
testOrderedCollectionWithFederatedId2.SetActivityStreamsOrderedItems(oi)
}()
// testOrderedCollectionWithBothFederatedIds
func() {
testOrderedCollectionWithBothFederatedIds = streams.NewActivityStreamsOrderedCollectionPage()
oi := streams.NewActivityStreamsOrderedItemsProperty()
oi.AppendIRI(mustParse(testFederatedActivityIRI))
oi.AppendIRI(mustParse(testFederatedActivityIRI2))
testOrderedCollectionWithBothFederatedIds.SetActivityStreamsOrderedItems(oi)
}()
}
// wrappedInCreate returns a Create activity wrapping the given type.

ファイルの表示

@ -105,12 +105,17 @@ func (a *sideEffectActor) PostInbox(c context.Context, inboxIRI *url.URL, activi
wrapped.db = a.db
wrapped.inboxIRI = inboxIRI
wrapped.newTransport = a.common.NewTransport
res, err := streams.NewTypeResolver(wrapped.callbacks(other))
res, err := streams.NewTypeResolver(wrapped.callbacks(other)...)
if err != nil {
return err
}
if err = res.Resolve(c, activity); err != nil {
if err = res.Resolve(c, activity); err != nil && !streams.IsUnmatchedErr(err) {
return err
} else if streams.IsUnmatchedErr(err) {
err = a.s2s.DefaultCallback(c, activity)
if err != nil {
return err
}
}
}
return nil
@ -291,7 +296,7 @@ func (a *sideEffectActor) InboxForwarding(c context.Context, inboxIRI *url.URL,
//
// This implementation assumes all types are meant to be delivered except for
// the ActivityStreams Block type.
func (a *sideEffectActor) PostOutbox(c context.Context, activity Activity, outboxIRI *url.URL, rawJSON map[string]interface{}) (deliverable bool, e error) {
func (a *sideEffectActor) PostOutbox(c context.Context, activity Activity, outboxIRI *url.URL, rawJSON map[string]interface{}) (deliverable bool, err error) {
wrapped, other := a.c2s.Callbacks(c)
// Populate side channels.
wrapped.db = a.db
@ -300,12 +305,19 @@ func (a *sideEffectActor) PostOutbox(c context.Context, activity Activity, outbo
wrapped.clock = a.clock
wrapped.newTransport = a.common.NewTransport
wrapped.deliverable = &deliverable
res, err := streams.NewTypeResolver(wrapped.callbacks(other))
var res *streams.TypeResolver
res, err = streams.NewTypeResolver(wrapped.callbacks(other)...)
if err != nil {
return
}
if err = res.Resolve(c, activity); err != nil {
if err = res.Resolve(c, activity); err != nil && !streams.IsUnmatchedErr(err) {
return
} else if streams.IsUnmatchedErr(err) {
deliverable = true
err = a.c2s.DefaultCallback(c, activity)
if err != nil {
return
}
}
err = a.addToOutbox(c, outboxIRI, activity)
return

ファイルの表示

@ -2,6 +2,7 @@ package pub
import (
"context"
"github.com/go-fed/activity/streams/vocab"
"github.com/golang/mock/gomock"
"net/http/httptest"
"net/url"
@ -11,10 +12,10 @@ import (
// TestPassThroughMethods tests the methods that pass-through to other
// dependency-injected types.
func TestPassThroughMethods(t *testing.T) {
setupData()
ctx := context.Background()
resp := httptest.NewRecorder()
setupFn := func(ctl *gomock.Controller) (c *MockCommonBehavior, fp *MockFederatingProtocol, sp *MockSocialProtocol, db *MockDatabase, cl *MockClock, a DelegateActor) {
setupData()
c = NewMockCommonBehavior(ctl)
fp = NewMockFederatingProtocol(ctl)
sp = NewMockSocialProtocol(ctl)
@ -22,10 +23,10 @@ func TestPassThroughMethods(t *testing.T) {
cl = NewMockClock(ctl)
a = &sideEffectActor{
common: c,
s2s: fp,
c2s: sp,
db: db,
clock: cl,
s2s: fp,
c2s: sp,
db: db,
clock: cl,
}
return
}
@ -113,10 +114,10 @@ func TestPassThroughMethods(t *testing.T) {
// TestAuthorizePostInbox tests the Authorization for a federated message, which
// is only based on blocks.
func TestAuthorizePostInbox(t *testing.T) {
setupData()
ctx := context.Background()
resp := httptest.NewRecorder()
setupFn := func(ctl *gomock.Controller) (c *MockCommonBehavior, fp *MockFederatingProtocol, sp *MockSocialProtocol, db *MockDatabase, cl *MockClock, a DelegateActor) {
setupData()
c = NewMockCommonBehavior(ctl)
fp = NewMockFederatingProtocol(ctl)
sp = NewMockSocialProtocol(ctl)
@ -124,10 +125,10 @@ func TestAuthorizePostInbox(t *testing.T) {
cl = NewMockClock(ctl)
a = &sideEffectActor{
common: c,
s2s: fp,
c2s: sp,
db: db,
clock: cl,
s2s: fp,
c2s: sp,
db: db,
clock: cl,
}
return
}
@ -185,23 +186,160 @@ func TestAuthorizePostInbox(t *testing.T) {
// TestPostInbox ensures that the main application side effects of receiving a
// federated message occur.
func TestPostInbox(t *testing.T) {
ctx := context.Background()
setupFn := func(ctl *gomock.Controller) (c *MockCommonBehavior, fp *MockFederatingProtocol, sp *MockSocialProtocol, db *MockDatabase, cl *MockClock, a DelegateActor) {
setupData()
c = NewMockCommonBehavior(ctl)
fp = NewMockFederatingProtocol(ctl)
sp = NewMockSocialProtocol(ctl)
db = NewMockDatabase(ctl)
cl = NewMockClock(ctl)
a = &sideEffectActor{
common: c,
s2s: fp,
c2s: sp,
db: db,
clock: cl,
}
return
}
// Run tests
t.Run("AddsToEmptyInbox", func(t *testing.T) {
t.Fail()
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, fp, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(false, nil),
db.EXPECT().GetInbox(ctx, inboxIRI).Return(testEmptyOrderedCollection, nil),
db.EXPECT().SetInbox(ctx, testOrderedCollectionWithFederatedId).Return(nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
fp.EXPECT().Callbacks(ctx).Return(FederatingWrappedCallbacks{}, nil)
fp.EXPECT().DefaultCallback(ctx, testListen).Return(nil)
// Run
err := a.PostInbox(ctx, inboxIRI, testListen)
// Verify
assertEqual(t, err, nil)
})
t.Run("DoesNotAddToInboxIfDuplicate", func(t *testing.T) {
t.Fail()
t.Run("DoesNotAddToInboxNorDoSideEffectsIfDuplicate", func(t *testing.T) {
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, _, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(true, nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
// Run
err := a.PostInbox(ctx, inboxIRI, testListen)
// Verify
assertEqual(t, err, nil)
})
t.Run("AddsToInbox", func(t *testing.T) {
t.Fail()
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, fp, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(false, nil),
db.EXPECT().GetInbox(ctx, inboxIRI).Return(testOrderedCollectionWithFederatedId2, nil),
db.EXPECT().SetInbox(ctx, testOrderedCollectionWithBothFederatedIds).Return(nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
fp.EXPECT().Callbacks(ctx).Return(FederatingWrappedCallbacks{}, nil)
fp.EXPECT().DefaultCallback(ctx, testListen).Return(nil)
// Run
err := a.PostInbox(ctx, inboxIRI, testListen)
// Verify
assertEqual(t, err, nil)
})
t.Run("ResolvesToCustomFunction", func(t *testing.T) {
t.Fail()
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, fp, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(false, nil),
db.EXPECT().GetInbox(ctx, inboxIRI).Return(testEmptyOrderedCollection, nil),
db.EXPECT().SetInbox(ctx, testOrderedCollectionWithFederatedId).Return(nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
pass := false
fp.EXPECT().Callbacks(ctx).Return(FederatingWrappedCallbacks{}, []interface{}{
func(c context.Context, a vocab.ActivityStreamsListen) error {
pass = true
return nil
},
})
// Run
err := a.PostInbox(ctx, inboxIRI, testListen)
// Verify
assertEqual(t, err, nil)
assertEqual(t, pass, true)
})
t.Run("ResolvesToOverriddenFunction", func(t *testing.T) {
t.Fail()
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, fp, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(false, nil),
db.EXPECT().GetInbox(ctx, inboxIRI).Return(testEmptyOrderedCollection, nil),
db.EXPECT().SetInbox(ctx, testOrderedCollectionWithFederatedId).Return(nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
pass := false
fp.EXPECT().Callbacks(ctx).Return(FederatingWrappedCallbacks{}, []interface{}{
func(c context.Context, a vocab.ActivityStreamsCreate) error {
pass = true
return nil
},
})
// Run
err := a.PostInbox(ctx, inboxIRI, testCreate)
// Verify
assertEqual(t, err, nil)
assertEqual(t, pass, true)
})
t.Run("ResolvesToDefaultFunction", func(t *testing.T) {
t.Fail()
// Setup
ctl := gomock.NewController(t)
defer ctl.Finish()
_, fp, _, db, _, a := setupFn(ctl)
inboxIRI := mustParse(testMyInboxIRI)
gomock.InOrder(
db.EXPECT().Lock(ctx, inboxIRI),
db.EXPECT().InboxContains(ctx, inboxIRI, mustParse(testFederatedActivityIRI)).Return(false, nil),
db.EXPECT().GetInbox(ctx, inboxIRI).Return(testEmptyOrderedCollection, nil),
db.EXPECT().SetInbox(ctx, testOrderedCollectionWithFederatedId).Return(nil),
db.EXPECT().Unlock(ctx, inboxIRI),
)
pass := false
fp.EXPECT().Callbacks(ctx).Return(FederatingWrappedCallbacks{
Create: func(c context.Context, a vocab.ActivityStreamsCreate) error {
pass = true
return nil
},
}, nil)
db.EXPECT().Lock(ctx, mustParse(testNoteId1))
db.EXPECT().Create(ctx, testFederatedNote)
db.EXPECT().Unlock(ctx, mustParse(testNoteId1))
// Run
err := a.PostInbox(ctx, inboxIRI, testCreate)
// Verify
assertEqual(t, err, nil)
assertEqual(t, pass, true)
})
}

ファイルの表示

@ -50,7 +50,18 @@ type SocialProtocol interface {
// To override the default behavior, instead supply the function in
// 'other', which does not guarantee the application will be compliant
// with the ActivityPub Social Protocol.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension. The unhandled ones are passed to DefaultCallback.
Callbacks(c context.Context) (wrapped SocialWrappedCallbacks, other []interface{})
// DefaultCallback is called for types that go-fed can deserialize but
// are not handled by the application's callbacks returned in the
// Callbacks method.
//
// Applications are not expected to handle every single ActivityStreams
// type and extension, so the unhandled ones are passed to
// DefaultCallback.
DefaultCallback(c context.Context, activity Activity) error
// GetOutbox returns the OrderedCollection inbox of the actor for this
// context. It is up to the implementation to provide the correct
// collection for the kind of authorization given in the request.