//go:generate go install github.com/go-fed/activity/tools/streams //go:generate streams package streams import ( "encoding/json" "github.com/go-fed/activity/vocab" "github.com/go-test/deep" "net/url" "testing" ) func TestRepoExamples(t *testing.T) { for name, ex := range allRepoExamples { resFn := func(s vocab.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 := &Resolver{ ObjectCallback: func(x *Object) error { return resFn(x) }, LinkCallback: func(x *Link) error { return resFn(x) }, ActivityCallback: func(x *Activity) error { return resFn(x) }, IntransitiveActivityCallback: func(x *IntransitiveActivity) error { return resFn(x) }, CollectionCallback: func(x *Collection) error { return resFn(x) }, OrderedCollectionCallback: func(x *OrderedCollection) error { return resFn(x) }, CollectionPageCallback: func(x *CollectionPage) error { return resFn(x) }, OrderedCollectionPageCallback: func(x *OrderedCollectionPage) error { return resFn(x) }, AcceptCallback: func(x *Accept) error { return resFn(x) }, TentativeAcceptCallback: func(x *TentativeAccept) error { return resFn(x) }, AddCallback: func(x *Add) error { return resFn(x) }, ArriveCallback: func(x *Arrive) error { return resFn(x) }, CreateCallback: func(x *Create) error { return resFn(x) }, DeleteCallback: func(x *Delete) error { return resFn(x) }, FollowCallback: func(x *Follow) error { return resFn(x) }, IgnoreCallback: func(x *Ignore) error { return resFn(x) }, JoinCallback: func(x *Join) error { return resFn(x) }, LeaveCallback: func(x *Leave) error { return resFn(x) }, LikeCallback: func(x *Like) error { return resFn(x) }, OfferCallback: func(x *Offer) error { return resFn(x) }, InviteCallback: func(x *Invite) error { return resFn(x) }, RejectCallback: func(x *Reject) error { return resFn(x) }, TentativeRejectCallback: func(x *TentativeReject) error { return resFn(x) }, RemoveCallback: func(x *Remove) error { return resFn(x) }, UndoCallback: func(x *Undo) error { return resFn(x) }, UpdateCallback: func(x *Update) error { return resFn(x) }, ViewCallback: func(x *View) error { return resFn(x) }, ListenCallback: func(x *Listen) error { return resFn(x) }, ReadCallback: func(x *Read) error { return resFn(x) }, MoveCallback: func(x *Move) error { return resFn(x) }, TravelCallback: func(x *Travel) error { return resFn(x) }, AnnounceCallback: func(x *Announce) error { return resFn(x) }, BlockCallback: func(x *Block) error { return resFn(x) }, FlagCallback: func(x *Flag) error { return resFn(x) }, DislikeCallback: func(x *Dislike) error { return resFn(x) }, QuestionCallback: func(x *Question) error { return resFn(x) }, ApplicationCallback: func(x *Application) error { return resFn(x) }, GroupCallback: func(x *Group) error { return resFn(x) }, OrganizationCallback: func(x *Organization) error { return resFn(x) }, PersonCallback: func(x *Person) error { return resFn(x) }, ServiceCallback: func(x *Service) error { return resFn(x) }, RelationshipCallback: func(x *Relationship) error { return resFn(x) }, ArticleCallback: func(x *Article) error { return resFn(x) }, DocumentCallback: func(x *Document) error { return resFn(x) }, AudioCallback: func(x *Audio) error { return resFn(x) }, ImageCallback: func(x *Image) error { return resFn(x) }, VideoCallback: func(x *Video) error { return resFn(x) }, NoteCallback: func(x *Note) error { return resFn(x) }, PageCallback: func(x *Page) error { return resFn(x) }, EventCallback: func(x *Event) error { return resFn(x) }, PlaceCallback: func(x *Place) error { return resFn(x) }, ProfileCallback: func(x *Profile) error { return resFn(x) }, TombstoneCallback: func(x *Tombstone) error { return resFn(x) }, MentionCallback: func(x *Mention) error { return resFn(x) }, } 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.Deserialize(m) if err != nil { t.Errorf("%s: Cannot Resolver.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 }