activity/streams/streams_test.go

603 行
18 KiB
Go

package streams
import (
"context"
"encoding/json"
"github.com/go-fed/activity/streams/vocab"
"github.com/go-test/deep"
"net/url"
"testing"
)
type serializer interface {
Serialize() (map[string]interface{}, error)
}
// IsKnownResolverError returns true if it is known that an example from
// GetTestTable will trigger a JSONResolver error.
func IsKnownResolverError(t TestTable) (b bool, reason string) {
if t.name == "Example 61" {
b = true
reason = "no \"type\" property is on the root object"
} else if t.name == "Example 62" {
b = true
reason = "an unknown \"type\" property is on the root object"
} else if t.name == "Example 153" {
b = true
reason = "no \"type\" property is on the root object"
}
return
}
func TestJSONResolver(t *testing.T) {
for _, example := range GetTestTable() {
if skip, reason := IsKnownResolverError(example); skip {
t.Logf("Skipping table test case %q as it's known an error will be returned because %s", example.name, reason)
continue
}
name := example.name
t.Logf("Testing table test case %q", name)
ex := example.expectedJSON
resFn := func(s serializer) error {
m, err := s.Serialize()
if err != nil {
return err
}
m["@context"] = "https://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(
func(c context.Context, x vocab.ActivityStreamsAccept) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsActivity) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAdd) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAnnounce) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsApplication) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsArrive) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsArticle) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAudio) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsBlock) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCollection) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCollectionPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCreate) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDelete) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDislike) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDocument) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsEvent) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsFlag) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsFollow) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsGroup) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsIgnore) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsImage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsIntransitiveActivity) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsInvite) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsJoin) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLeave) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLike) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLink) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsListen) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsMention) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsMove) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsNote) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsObject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOffer) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrderedCollection) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrderedCollectionPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrganization) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPerson) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPlace) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsProfile) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsQuestion) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRead) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsReject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRelationship) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRemove) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsService) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTentativeAccept) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTentativeReject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTombstone) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTravel) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsUndo) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsUpdate) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsVideo) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsView) 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
}
err = r.Resolve(context.Background(), m)
if err != nil {
t.Errorf("%s: Cannot JSONResolver.Deserialize: %s", name, err)
continue
}
}
}
func TestJSONResolverErrors(t *testing.T) {
for _, example := range GetTestTable() {
isError, reason := IsKnownResolverError(example)
if !isError {
continue
}
name := example.name
t.Logf("Testing table test case %q", name)
ex := example.expectedJSON
resFn := func(s serializer) error { return nil }
r, err := NewJSONResolver(
func(c context.Context, x vocab.ActivityStreamsAccept) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsActivity) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAdd) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAnnounce) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsApplication) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsArrive) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsArticle) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsAudio) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsBlock) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCollection) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCollectionPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsCreate) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDelete) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDislike) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsDocument) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsEvent) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsFlag) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsFollow) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsGroup) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsIgnore) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsImage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsIntransitiveActivity) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsInvite) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsJoin) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLeave) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLike) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsLink) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsListen) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsMention) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsMove) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsNote) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsObject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOffer) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrderedCollection) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrderedCollectionPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsOrganization) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPage) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPerson) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsPlace) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsProfile) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsQuestion) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRead) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsReject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRelationship) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsRemove) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsService) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTentativeAccept) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTentativeReject) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTombstone) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsTravel) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsUndo) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsUpdate) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsVideo) error {
return resFn(x)
},
func(c context.Context, x vocab.ActivityStreamsView) 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
}
err = r.Resolve(context.Background(), m)
if err == nil {
t.Errorf("%s: Expected error because %s", name, reason)
}
}
}
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)
}
noteIdProperty := NewActivityStreamsIdProperty()
noteIdProperty.SetIRI(noteIRI)
expectedNote := NewActivityStreamsNote()
expectedNote.SetActivityStreamsId(noteIdProperty)
noteNameProperty := NewActivityStreamsNameProperty()
noteNameProperty.AppendXMLSchemaString("A Note")
expectedNote.SetActivityStreamsName(noteNameProperty)
noteContentProperty := NewActivityStreamsContentProperty()
noteContentProperty.AppendXMLSchemaString("This is a simple note")
expectedNote.SetActivityStreamsContent(noteContentProperty)
noteToProperty := NewActivityStreamsToProperty()
expectedSamActor := NewActivityStreamsPerson()
samInboxProperty := NewActivityStreamsInboxProperty()
samInboxProperty.SetIRI(samIRIInbox)
expectedSamActor.SetActivityStreamsInbox(samInboxProperty)
samIdProperty := NewActivityStreamsIdProperty()
samIdProperty.SetIRI(samIRI)
expectedSamActor.SetActivityStreamsId(samIdProperty)
noteToProperty.AppendActivityStreamsPerson(expectedSamActor)
expectedNote.SetActivityStreamsTo(noteToProperty)
expectedUpdate := NewActivityStreamsUpdate()
sallyIdProperty := NewActivityStreamsIdProperty()
sallyIdProperty.SetIRI(sallyIRI)
sallyPerson := NewActivityStreamsPerson()
sallyPerson.SetActivityStreamsId(sallyIdProperty)
sallyActor := NewActivityStreamsActorProperty()
sallyActor.AppendActivityStreamsPerson(sallyPerson)
expectedUpdate.SetActivityStreamsActor(sallyActor)
summaryProperty := NewActivityStreamsSummaryProperty()
summaryProperty.AppendXMLSchemaString("Sally updated her note")
expectedUpdate.SetActivityStreamsSummary(summaryProperty)
updateIdProperty := NewActivityStreamsIdProperty()
updateIdProperty.SetIRI(activityIRI)
expectedUpdate.SetActivityStreamsId(updateIdProperty)
objectNote := NewActivityStreamsObjectProperty()
objectNote.AppendActivityStreamsNote(expectedNote)
expectedUpdate.SetActivityStreamsObject(objectNote)
// Variable to aid in deserialization in tests
var actual serializer
tables := []struct {
name string
expected serializer
callback interface{}
input string
inputWithoutNulls string
}{
{
name: "JSON nulls are not preserved",
expected: expectedUpdate,
callback: func(c context.Context, v vocab.ActivityStreamsUpdate) error {
actual = v
return nil
},
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
}
}
}
`,
inputWithoutNulls: `
{
"@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"
}
}
}
`,
},
}
for _, r := range tables {
t.Logf("Testing table test case %q", r.name)
res, err := NewJSONResolver(r.callback)
if err != nil {
t.Errorf("%s: cannot create resolver: %s", r.name, err)
continue
}
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.Resolve(context.Background(), 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)
}
m, err = actual.Serialize()
if err != nil {
t.Errorf("%s: Cannot Serialize: %s", r.name, err)
continue
}
m["@context"] = "https://www.w3.org/ns/activitystreams"
reser, err := json.Marshal(m)
if err != nil {
t.Errorf("%s: Cannot json.Marshal: %s", r.name, err)
continue
}
if diff, err := GetJSONDiff(reser, []byte(r.inputWithoutNulls)); err == nil && diff != nil {
t.Errorf("%s: Serialize JSON equality is false:\n%s", r.name, diff)
} else if err != nil {
t.Errorf("%s: GetJSONDiff returned error: %s", r.name, err)
}
}
}
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
}