package streams import ( "context" "encoding/json" "github.com/go-fed/activity/streams/vocab" "github.com/go-test/deep" "testing" ) type serializer interface { Serialize() (map[string]interface{}, error) } func TestJSONResolver(t *testing.T) { for _, example := range allRepoExamples { name := example.name ex := example.example resFn := func(s serializer) error { m, err := s.Serialize() if err != nil { return err } m["@context"] = "http://www.w3.org/ns/activitystreams" actual, err := json.Marshal(m) if diff, err := GetJSONDiff(actual, []byte(ex)); err == nil && diff != nil { t.Errorf("%s: Serialize JSON equality is false:\n%s", name, diff) } else if err != nil { t.Errorf("%s: GetJSONDiff returned error: %s", name, err) } return nil } r, err := NewJSONResolver([]interface{}{ func(c context.Context, x vocab.ObjectInterface) error { return resFn(x) }, func(c context.Context, x vocab.LinkInterface) error { return resFn(x) }, func(c context.Context, x vocab.ActivityInterface) error { return resFn(x) }, func(c context.Context, x vocab.IntransitiveActivityInterface) error { return resFn(x) }, func(c context.Context, x vocab.CollectionInterface) error { return resFn(x) }, func(c context.Context, x vocab.OrderedCollectionInterface) error { return resFn(x) }, func(c context.Context, x vocab.CollectionPageInterface) error { return resFn(x) }, func(c context.Context, x vocab.OrderedCollectionPageInterface) error { return resFn(x) }, func(c context.Context, x vocab.AcceptInterface) error { return resFn(x) }, func(c context.Context, x vocab.TentativeAcceptInterface) error { return resFn(x) }, func(c context.Context, x vocab.AddInterface) error { return resFn(x) }, func(c context.Context, x vocab.ArriveInterface) error { return resFn(x) }, func(c context.Context, x vocab.CreateInterface) error { return resFn(x) }, func(c context.Context, x vocab.DeleteInterface) error { return resFn(x) }, func(c context.Context, x vocab.FollowInterface) error { return resFn(x) }, func(c context.Context, x vocab.IgnoreInterface) error { return resFn(x) }, func(c context.Context, x vocab.JoinInterface) error { return resFn(x) }, func(c context.Context, x vocab.LeaveInterface) error { return resFn(x) }, func(c context.Context, x vocab.LikeInterface) error { return resFn(x) }, func(c context.Context, x vocab.OfferInterface) error { return resFn(x) }, func(c context.Context, x vocab.InviteInterface) error { return resFn(x) }, func(c context.Context, x vocab.RejectInterface) error { return resFn(x) }, func(c context.Context, x vocab.TentativeRejectInterface) error { return resFn(x) }, func(c context.Context, x vocab.RemoveInterface) error { return resFn(x) }, func(c context.Context, x vocab.UndoInterface) error { return resFn(x) }, func(c context.Context, x vocab.UpdateInterface) error { return resFn(x) }, func(c context.Context, x vocab.ViewInterface) error { return resFn(x) }, func(c context.Context, x vocab.ListenInterface) error { return resFn(x) }, func(c context.Context, x vocab.ReadInterface) error { return resFn(x) }, func(c context.Context, x vocab.MoveInterface) error { return resFn(x) }, func(c context.Context, x vocab.TravelInterface) error { return resFn(x) }, func(c context.Context, x vocab.AnnounceInterface) error { return resFn(x) }, func(c context.Context, x vocab.BlockInterface) error { return resFn(x) }, func(c context.Context, x vocab.FlagInterface) error { return resFn(x) }, func(c context.Context, x vocab.DislikeInterface) error { return resFn(x) }, func(c context.Context, x vocab.QuestionInterface) error { return resFn(x) }, func(c context.Context, x vocab.ApplicationInterface) error { return resFn(x) }, func(c context.Context, x vocab.GroupInterface) error { return resFn(x) }, func(c context.Context, x vocab.OrganizationInterface) error { return resFn(x) }, func(c context.Context, x vocab.PersonInterface) error { return resFn(x) }, func(c context.Context, x vocab.ServiceInterface) error { return resFn(x) }, func(c context.Context, x vocab.RelationshipInterface) error { return resFn(x) }, func(c context.Context, x vocab.ArticleInterface) error { return resFn(x) }, func(c context.Context, x vocab.DocumentInterface) error { return resFn(x) }, func(c context.Context, x vocab.AudioInterface) error { return resFn(x) }, func(c context.Context, x vocab.ImageInterface) error { return resFn(x) }, func(c context.Context, x vocab.VideoInterface) error { return resFn(x) }, func(c context.Context, x vocab.NoteInterface) error { return resFn(x) }, func(c context.Context, x vocab.PageInterface) error { return resFn(x) }, func(c context.Context, x vocab.EventInterface) error { return resFn(x) }, func(c context.Context, x vocab.PlaceInterface) error { return resFn(x) }, func(c context.Context, x vocab.ProfileInterface) error { return resFn(x) }, func(c context.Context, x vocab.TombstoneInterface) error { return resFn(x) }, func(c context.Context, x vocab.MentionInterface) error { return resFn(x) }, }) if err != nil { t.Errorf("%s: Cannot create JSONResolver: %s", name, err) continue } m := make(map[string]interface{}) err = json.Unmarshal([]byte(ex), &m) if err != nil { t.Errorf("%s: Cannot json.Unmarshal: %s", name, err) continue } // Examples that needed adjustment: // Example 64: Array of one attachment - deserializes OK, but re-serialization does not match // Example 68: Array of one bcc - deserializes OK, but re-serialization does not match // Example 70: Array of one cc - deserializes OK, but re-serialization does not match // Example 112: Array of one item - deserializes OK, but re-serialization does not match // Example 118: Array of one tag - deserializes OK, but re-serialization does not match // Example 123: Array of one to - deserializes OK, but re-serialization does not match // Example 184: missing @context // Example 196: '\n' characters were literal newlines in source err = r.Resolve(context.Background(), m) if err != nil { t.Errorf("%s: Cannot JSONResolver.Deserialize: %s", name, err) continue } } } /* func TestNulls(t *testing.T) { const ( samIRIInboxString = "https://example.com/sam/inbox" samIRIString = "https://example.com/sam" noteURIString = "https://example.com/note/123" sallyIRIString = "https://example.com/sally" activityIRIString = "https://example.com/test/new/iri" ) samIRIInbox, err := url.Parse(samIRIInboxString) if err != nil { t.Fatal(err) } samIRI, err := url.Parse(samIRIString) if err != nil { t.Fatal(err) } noteIRI, err := url.Parse(noteURIString) if err != nil { t.Fatal(err) } sallyIRI, err := url.Parse(sallyIRIString) if err != nil { t.Fatal(err) } activityIRI, err := url.Parse(activityIRIString) if err != nil { t.Fatal(err) } expectedSamActor := &vocab.Person{} expectedSamActor.SetInboxAnyURI(samIRIInbox) expectedSamActor.SetId(samIRI) expectedNote := &vocab.Note{} expectedNote.SetId(noteIRI) expectedNote.AppendNameString("A Note") expectedNote.AppendContentString("This is a simple note") expectedNote.AppendToObject(expectedSamActor) expectedUpdate := &vocab.Update{} expectedUpdate.AppendActorIRI(sallyIRI) expectedUpdate.AppendSummaryString("Sally updated her note") expectedUpdate.SetId(activityIRI) expectedUpdate.AppendObject(expectedNote) tables := []struct { name string expected vocab.Serializer input string }{ { name: "JSON with null", expected: expectedUpdate, input: ` { "@context": "https://www.w3.org/ns/activitystreams", "summary": "Sally updated her note", "type": "Update", "actor": "https://example.com/sally", "id": "https://example.com/test/new/iri", "object": { "id": "https://example.com/note/123", "type": "Note", "to": { "id": "https://example.com/sam", "inbox": "https://example.com/sam/inbox", "type": "Person", "name": null } } } `, }, } for _, r := range tables { var actual vocab.ActivityType res := &Resolver{ AnyActivityCallback: func(v vocab.ActivityType) error { actual = v return nil }, } m := make(map[string]interface{}) err := json.Unmarshal([]byte(r.input), &m) if err != nil { t.Errorf("%s: Cannot json.Unmarshal: %s", r.name, err) continue } err = res.Deserialize(m) if err != nil { t.Errorf("%s: Cannot Deserialize: %s", r.name, err) continue } if diff := deep.Equal(actual, r.expected); diff != nil { t.Errorf("%s: Deserialize deep equal is false: %s", r.name, diff) } // Only test ability to reserialize serializer, ok := actual.(vocab.Serializer) if !ok { t.Errorf("Deserializer is not also Serializer") } m, err = serializer.Serialize() if err != nil { t.Errorf("%s: Cannot Serialize: %s", r.name, err) continue } m["@context"] = "https://www.w3.org/ns/activitystreams" _, err = json.Marshal(m) if err != nil { t.Errorf("%s: Cannot json.Marshal: %s", r.name, err) continue } } } */ func GetJSONDiff(str1, str2 []byte) ([]string, error) { var i1 interface{} var i2 interface{} err := json.Unmarshal(str1, &i1) if err != nil { return nil, err } err = json.Unmarshal(str2, &i2) if err != nil { return nil, err } return deep.Equal(i1, i2), nil }