diff --git a/pub/pub_test.go b/pub/pub_test.go index 10129d6..387596c 100644 --- a/pub/pub_test.go +++ b/pub/pub_test.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "github.com/go-fed/activity/streams" "github.com/go-fed/activity/streams/vocab" "net/http" @@ -24,6 +25,14 @@ const ( testNoteId1 = "https://example.com/note/1" testNoteId2 = "https://example.com/note/2" testNewActivityIRI = "https://example.com/new/1" + testToIRI = "https://maybe.example.com/to/1" + testToIRI2 = "https://maybe.example.com/to/2" + testCcIRI = "https://maybe.example.com/cc/1" + testCcIRI2 = "https://maybe.example.com/cc/2" + testAudienceIRI = "https://maybe.example.com/audience/1" + testAudienceIRI2 = "https://maybe.example.com/audience/2" + testPersonIRI = "https://maybe.example.com/person" + testServiceIRI = "https://maybe.example.com/service" ) // mustParse parses a URL or panics. @@ -91,6 +100,10 @@ var ( testOrderedCollectionWithFederatedId2 vocab.ActivityStreamsOrderedCollectionPage // testOrderedCollectionWithBothFederatedIds has both federated Activity id. testOrderedCollectionWithBothFederatedIds vocab.ActivityStreamsOrderedCollectionPage + // testPerson is a Person. + testPerson vocab.ActivityStreamsPerson + // testService is a Service. + testService vocab.ActivityStreamsService ) // The test data cannot be created at init time since that is when the hooks of @@ -216,6 +229,20 @@ func setupData() { oi.AppendIRI(mustParse(testFederatedActivityIRI2)) testOrderedCollectionWithBothFederatedIds.SetActivityStreamsOrderedItems(oi) }() + // testPerson + func () { + testPerson = streams.NewActivityStreamsPerson() + id := streams.NewActivityStreamsIdProperty() + id.Set(mustParse(testPersonIRI)) + testPerson.SetActivityStreamsId(id) + }() + // testService + func () { + testService = streams.NewActivityStreamsService() + id := streams.NewActivityStreamsIdProperty() + id.Set(mustParse(testServiceIRI)) + testService.SetActivityStreamsId(id) + } () } // wrappedInCreate returns a Create activity wrapping the given type. @@ -364,3 +391,38 @@ func toGetInboxRequest() *http.Request { func toGetOutboxRequest() *http.Request { return httptest.NewRequest("GET", testMyOutboxIRI, nil) } + +// addToIds adds two IRIs to the 'to' property +func addToIds(t Activity) Activity { + to := streams.NewActivityStreamsToProperty() + to.AppendIRI(mustParse(testToIRI)) + to.AppendIRI(mustParse(testToIRI2)) + t.SetActivityStreamsTo(to) + return t +} + +// mustAddCcIds adds two IRIs to the 'cc' property +func mustAddCcIds(t Activity) Activity { + if ccer, ok := t.(ccer); ok { + cc := streams.NewActivityStreamsCcProperty() + cc.AppendIRI(mustParse(testCcIRI)) + cc.AppendIRI(mustParse(testCcIRI2)) + ccer.SetActivityStreamsCc(cc) + } else { + panic(fmt.Sprintf("activity is not ccer: %T", t)) + } + return t +} + +// mustAddAudienceIds adds two IRIs to the 'audience' property +func mustAddAudienceIds(t Activity) Activity { + if audiencer, ok := t.(audiencer); ok { + aud := streams.NewActivityStreamsAudienceProperty() + aud.AppendIRI(mustParse(testAudienceIRI)) + aud.AppendIRI(mustParse(testAudienceIRI2)) + audiencer.SetActivityStreamsAudience(aud) + } else { + panic(fmt.Sprintf("activity is not audiencer: %T", t)) + } + return t +} diff --git a/pub/side_effect_actor.go b/pub/side_effect_actor.go index 0098532..bacb457 100644 --- a/pub/side_effect_actor.go +++ b/pub/side_effect_actor.go @@ -160,28 +160,34 @@ func (a *sideEffectActor) InboxForwarding(c context.Context, inboxIRI *url.URL, // this server. var r []*url.URL to := activity.GetActivityStreamsTo() - for iter := to.Begin(); iter != to.End(); iter = iter.Next() { - val, err := ToId(iter) - if err != nil { - return err + if to != nil { + for iter := to.Begin(); iter != to.End(); iter = iter.Next() { + val, err := ToId(iter) + if err != nil { + return err + } + r = append(r, val) } - r = append(r, val) } cc := activity.GetActivityStreamsCc() - for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() { - val, err := ToId(iter) - if err != nil { - return err + if cc != nil { + for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() { + val, err := ToId(iter) + if err != nil { + return err + } + r = append(r, val) } - r = append(r, val) } audience := activity.GetActivityStreamsAudience() - for iter := audience.Begin(); iter != audience.End(); iter = iter.Next() { - val, err := ToId(iter) - if err != nil { - return err + if audience != nil { + for iter := audience.Begin(); iter != audience.End(); iter = iter.Next() { + val, err := ToId(iter) + if err != nil { + return err + } + r = append(r, val) } - r = append(r, val) } // Find all IRIs owned by this server. We need to find all of them so // that forwarding can properly occur. diff --git a/pub/side_effect_actor_test.go b/pub/side_effect_actor_test.go index 994478d..5e7acc6 100644 --- a/pub/side_effect_actor_test.go +++ b/pub/side_effect_actor_test.go @@ -345,41 +345,217 @@ func TestPostInbox(t *testing.T) { // TestInboxForwarding ensures that the inbox forwarding logic is correct. func TestInboxForwarding(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 + } t.Run("DoesNotForwardIfAlreadyExists", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), testListen) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfToCollectionNotOwned", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := addToIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testToIRI)), + db.EXPECT().Owns(ctx, mustParse(testToIRI)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI)), + db.EXPECT().Lock(ctx, mustParse(testToIRI2)), + db.EXPECT().Owns(ctx, mustParse(testToIRI2)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI2)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfCcCollectionNotOwned", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := mustAddCcIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI)), + db.EXPECT().Owns(ctx, mustParse(testCcIRI)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI2)), + db.EXPECT().Owns(ctx, mustParse(testCcIRI2)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI2)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfAudienceCollectionNotOwned", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := mustAddAudienceIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI2)), + db.EXPECT().Owns(ctx, mustParse(testAudienceIRI2)).Return(false, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfToOwnedButNotCollection", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := addToIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testToIRI)), + db.EXPECT().Owns(ctx, mustParse(testToIRI)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI)), + db.EXPECT().Lock(ctx, mustParse(testToIRI2)), + db.EXPECT().Owns(ctx, mustParse(testToIRI2)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI2)), + db.EXPECT().Lock(ctx, mustParse(testToIRI)), + db.EXPECT().Get(ctx, mustParse(testToIRI)).Return(testPerson, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI)), + db.EXPECT().Lock(ctx, mustParse(testToIRI2)), + db.EXPECT().Get(ctx, mustParse(testToIRI2)).Return(testService, nil), + db.EXPECT().Unlock(ctx, mustParse(testToIRI2)), + // Deferred + db.EXPECT().Unlock(ctx, mustParse(testToIRI2)), + db.EXPECT().Unlock(ctx, mustParse(testToIRI)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfCcOwnedButNotCollection", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := mustAddCcIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI)), + db.EXPECT().Owns(ctx, mustParse(testCcIRI)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI2)), + db.EXPECT().Owns(ctx, mustParse(testCcIRI2)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI2)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI)), + db.EXPECT().Get(ctx, mustParse(testCcIRI)).Return(testPerson, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI)), + db.EXPECT().Lock(ctx, mustParse(testCcIRI2)), + db.EXPECT().Get(ctx, mustParse(testCcIRI2)).Return(testService, nil), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI2)), + // Deferred + db.EXPECT().Unlock(ctx, mustParse(testCcIRI2)), + db.EXPECT().Unlock(ctx, mustParse(testCcIRI)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfAudienceOwnedButNotCollection", func(t *testing.T) { - t.Fail() + // Setup + ctl := gomock.NewController(t) + defer ctl.Finish() + _, _, _, db, _, a := setupFn(ctl) + input := mustAddAudienceIds(testListen) + gomock.InOrder( + db.EXPECT().Lock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Exists(ctx, mustParse(testFederatedActivityIRI)).Return(false, nil), + db.EXPECT().Create(ctx, input).Return(nil), + db.EXPECT().Unlock(ctx, mustParse(testFederatedActivityIRI)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Owns(ctx, mustParse(testAudienceIRI)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI2)), + db.EXPECT().Owns(ctx, mustParse(testAudienceIRI2)).Return(true, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Get(ctx, mustParse(testAudienceIRI)).Return(testPerson, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI)), + db.EXPECT().Lock(ctx, mustParse(testAudienceIRI2)), + db.EXPECT().Get(ctx, mustParse(testAudienceIRI2)).Return(testService, nil), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2)), + // Deferred + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI2)), + db.EXPECT().Unlock(ctx, mustParse(testAudienceIRI)), + ) + // Run + err := a.InboxForwarding(ctx, mustParse(testMyInboxIRI), input) + // Verify + assertEqual(t, err, nil) }) t.Run("DoesNotForwardIfNoChainIsOwned", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ForwardsToRecipients", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ForwardsToRecipientsIfChainIsNested", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotForwardIfChainIsNestedTooDeep", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ForwardsToRecipientsIfChainNeedsDereferencing", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) } @@ -387,19 +563,19 @@ func TestInboxForwarding(t *testing.T) { // social protocol message occur. func TestPostOutbox(t *testing.T) { t.Run("AddsToEmptyOutbox", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("AddsToOutbox", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ResolvesToCustomFunction", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ResolvesToOverriddenFunction", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ResolvesToDefaultFunction", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) } @@ -407,13 +583,13 @@ func TestPostOutbox(t *testing.T) { // of its 'object' property values if it is a Create activity. func TestAddNewIds(t *testing.T) { t.Run("AddsIdToActivity", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("AddsIdsToObjectsIfCreateActivity", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotAddIdsToObjectsIfNotCreateActivity", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) } @@ -421,49 +597,49 @@ func TestAddNewIds(t *testing.T) { // the ActivityPub specification. func TestDeliver(t *testing.T) { t.Run("SendToRecipientsInTo", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("SendToRecipientsInBto", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("SendToRecipientsInCc", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("SendToRecipientsInBcc", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("SendToRecipientsInAudience", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotSendToPublicIRI", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("RecursivelyResolveCollectionActors", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("RecursivelyResolveOrderedCollectionActors", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotRecursivelyResolveCollectionActorsIfExceedingMaxDepth", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotSendIfMoreThanOneAttributedTo", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DoesNotSendIfThisActorIsNotInAttributedTo", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("DedupesRecipients", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("StripsBto", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("StripsBcc", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("ReturnsErrorIfAnyTransportRequestsFail", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) } @@ -471,27 +647,27 @@ func TestDeliver(t *testing.T) { // properly wrapped in a Create Activity. func TestWrapInCreate(t *testing.T) { t.Run("CreateHasTo", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasCc", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasBto", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasBcc", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasAudience", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasPublished", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasActor", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) t.Run("CreateHasObject", func(t *testing.T) { - t.Fail() + t.Errorf("Not yet implemented.") }) }