2018-06-07 14:48:08 +00:00
package tests
import (
"bytes"
"fmt"
"io"
"os"
2018-10-11 09:26:00 +00:00
"path/filepath"
2018-06-07 14:48:08 +00:00
"reflect"
"testing"
2018-08-06 17:11:24 +00:00
"time"
2018-08-05 11:54:01 +00:00
"unsafe"
2018-06-07 14:48:08 +00:00
2018-10-11 09:26:00 +00:00
ap "github.com/mariusor/activitypub.go/activitypub"
a "github.com/mariusor/activitypub.go/activitystreams"
2018-07-06 10:48:04 +00:00
j "github.com/mariusor/activitypub.go/jsonld"
2018-06-07 14:48:08 +00:00
)
const dir = "./mocks"
var stopOnFailure = false
type testPair struct {
expected bool
2018-08-31 11:38:15 +00:00
blank interface { }
result interface { }
2018-06-07 14:48:08 +00:00
}
type tests map [ string ] testPair
2018-08-05 11:54:01 +00:00
type visit struct {
a1 unsafe . Pointer
a2 unsafe . Pointer
typ reflect . Type
}
2018-08-06 13:26:50 +00:00
type canErrorFunc func ( format string , args ... interface { } )
2018-08-05 11:54:01 +00:00
2018-08-06 13:26:50 +00:00
func assertDeepEquals ( t canErrorFunc , x , y interface { } ) bool {
2018-08-05 11:54:01 +00:00
if x == nil || y == nil {
return x == y
}
v1 := reflect . ValueOf ( x )
2018-12-02 15:50:40 +00:00
//if v1.CanAddr() {
// v1 = v1.Addr()
//}
2018-08-05 11:54:01 +00:00
v2 := reflect . ValueOf ( y )
2018-12-02 15:50:40 +00:00
//if v2.CanAddr() {
// v2 = v2.Addr()
//}
2018-08-05 11:54:01 +00:00
if v1 . Type ( ) != v2 . Type ( ) {
t ( "%T != %T" , x , y )
return false
}
return deepValueEqual ( t , v1 , v2 , make ( map [ visit ] bool ) , 0 )
}
2018-08-06 13:26:50 +00:00
func deepValueEqual ( t canErrorFunc , v1 , v2 reflect . Value , visited map [ visit ] bool , depth int ) bool {
2018-08-05 11:54:01 +00:00
if ! v1 . IsValid ( ) || ! v2 . IsValid ( ) {
return v1 . IsValid ( ) == v2 . IsValid ( )
}
if v1 . Type ( ) != v2 . Type ( ) {
t ( "types differ %s != %s" , v1 . Type ( ) . Name ( ) , v2 . Type ( ) . Name ( ) )
return false
}
// We want to avoid putting more in the visited map than we need to.
// For any possible reference cycle that might be encountered,
// hard(t) needs to return true for at least one of the types in the cycle.
hard := func ( k reflect . Kind ) bool {
switch k {
case reflect . Map , reflect . Slice , reflect . Ptr , reflect . Interface :
return true
}
//t("Invalid type for %s", k)
return false
}
if v1 . CanAddr ( ) && v2 . CanAddr ( ) && hard ( v1 . Kind ( ) ) {
addr1 := unsafe . Pointer ( v1 . UnsafeAddr ( ) )
addr2 := unsafe . Pointer ( v2 . UnsafeAddr ( ) )
if uintptr ( addr1 ) > uintptr ( addr2 ) {
// Canonicalize order to reduce number of entries in visited.
// Assumes non-moving garbage collector.
addr1 , addr2 = addr2 , addr1
}
// Short circuit if references are already seen.
typ := v1 . Type ( )
v := visit { addr1 , addr2 , typ }
if visited [ v ] {
return true
}
// Remember for later.
visited [ v ] = true
}
switch v1 . Kind ( ) {
case reflect . Array :
for i := 0 ; i < v1 . Len ( ) ; i ++ {
if ! deepValueEqual ( t , v1 . Index ( i ) , v2 . Index ( i ) , visited , depth + 1 ) {
t ( "Arrays not equal at index %d %s %s" , i , v1 . Index ( i ) , v2 . Index ( i ) )
return false
}
}
return true
case reflect . Slice :
if v1 . IsNil ( ) != v2 . IsNil ( ) {
t ( "One of the slices is not nil %s[%d] vs %s[%d]" , v1 . Type ( ) . Name ( ) , v1 . Len ( ) , v2 . Type ( ) . Name ( ) , v2 . Len ( ) )
return false
}
if v1 . Len ( ) != v2 . Len ( ) {
t ( "Slices lengths are different %s[%d] vs %s[%d]" , v1 . Type ( ) . Name ( ) , v1 . Len ( ) , v2 . Type ( ) . Name ( ) , v2 . Len ( ) )
return false
}
if v1 . Pointer ( ) == v2 . Pointer ( ) {
return true
}
for i := 0 ; i < v1 . Len ( ) ; i ++ {
if ! deepValueEqual ( t , v1 . Index ( i ) , v2 . Index ( i ) , visited , depth + 1 ) {
t ( "Slices elements at pos %d are not equal %#v vs %#v" , i , v1 . Index ( i ) , v2 . Index ( i ) )
return false
}
}
return true
case reflect . Interface :
if v1 . IsNil ( ) || v2 . IsNil ( ) {
2018-08-06 11:06:14 +00:00
if v1 . IsNil ( ) == v2 . IsNil ( ) {
return true
}
var isNil1 , isNil2 string
if v1 . IsNil ( ) {
isNil1 = "is"
} else {
isNil1 = "isn't"
}
if v2 . IsNil ( ) {
isNil2 = "is"
} else {
isNil2 = "isn't"
}
t ( "Interface %q %s nil and %q %s nil" , v1 . Type ( ) . Name ( ) , isNil1 , v2 . Type ( ) . Name ( ) , isNil2 )
return false
2018-08-05 11:54:01 +00:00
}
return deepValueEqual ( t , v1 . Elem ( ) , v2 . Elem ( ) , visited , depth + 1 )
case reflect . Ptr :
if v1 . Pointer ( ) == v2 . Pointer ( ) {
return true
}
return deepValueEqual ( t , v1 . Elem ( ) , v2 . Elem ( ) , visited , depth + 1 )
case reflect . Struct :
for i , n := 0 , v1 . NumField ( ) ; i < n ; i ++ {
if ! deepValueEqual ( t , v1 . Field ( i ) , v2 . Field ( i ) , visited , depth + 1 ) {
2018-08-06 11:06:14 +00:00
t ( "Struct fields at pos %d %q:%q and %q:%q are not deeply equal" , i , v1 . Type ( ) . Field ( i ) . Name , v1 . Field ( i ) . Type ( ) . Name ( ) , v2 . Type ( ) . Field ( i ) . Name , v2 . Field ( i ) . Type ( ) . Name ( ) )
2018-09-06 19:56:46 +00:00
if v1 . Field ( i ) . CanAddr ( ) && v2 . Field ( i ) . CanAddr ( ) {
t ( " Values: %#v - %#v" , v1 . Field ( i ) . Interface ( ) , v2 . Field ( i ) . Interface ( ) )
}
2018-08-05 11:54:01 +00:00
return false
}
}
return true
case reflect . Map :
if v1 . IsNil ( ) != v2 . IsNil ( ) {
2018-08-06 11:06:14 +00:00
t ( "Maps are not nil" , v1 . Type ( ) . Name ( ) , v2 . Type ( ) . Name ( ) )
2018-08-05 11:54:01 +00:00
return false
}
if v1 . Len ( ) != v2 . Len ( ) {
2018-08-06 11:06:14 +00:00
t ( "Maps don't have the same length %d vs %d" , v1 . Len ( ) , v2 . Len ( ) )
2018-08-05 11:54:01 +00:00
return false
}
if v1 . Pointer ( ) == v2 . Pointer ( ) {
return true
}
for _ , k := range v1 . MapKeys ( ) {
val1 := v1 . MapIndex ( k )
val2 := v2 . MapIndex ( k )
if ! val1 . IsValid ( ) || ! val2 . IsValid ( ) || ! deepValueEqual ( t , v1 . MapIndex ( k ) , v2 . MapIndex ( k ) , visited , depth + 1 ) {
2018-08-06 11:06:14 +00:00
t ( "Maps values at index %s are not equal" , k . String ( ) )
2018-08-05 11:54:01 +00:00
return false
}
}
return true
case reflect . Func :
if v1 . IsNil ( ) && v2 . IsNil ( ) {
return true
}
// Can't do better than this:
return false
}
return true // i guess?
}
2018-08-06 17:11:24 +00:00
var zLoc , _ = time . LoadLocation ( "UTC" )
2018-06-09 11:08:30 +00:00
var allTests = tests {
"empty" : testPair {
2018-06-07 14:48:08 +00:00
expected : true ,
2018-06-09 12:05:46 +00:00
blank : & a . Object { } ,
result : & a . Object { } ,
2018-06-09 11:08:30 +00:00
} ,
"link_simple" : testPair {
expected : true ,
blank : & a . Link { } ,
result : & a . Link {
Type : a . LinkType ,
2018-10-11 18:13:34 +00:00
Href : a . IRI ( "http://example.org/abc" ) ,
2018-06-09 11:08:30 +00:00
HrefLang : a . LangRef ( "en" ) ,
MediaType : a . MimeType ( "text/html" ) ,
2018-09-02 13:41:53 +00:00
Name : a . NaturalLanguageValue { {
a . NilLangRef , "An example link" ,
} } ,
2018-06-09 11:08:30 +00:00
} ,
} ,
2018-07-24 21:11:08 +00:00
"object_with_url" : testPair {
expected : true ,
blank : & a . Object { } ,
result : & a . Object {
2018-10-11 18:13:34 +00:00
URL : a . IRI ( "http://littr.git/api/accounts/system" ) ,
2018-07-24 21:11:08 +00:00
} ,
} ,
2018-10-31 15:26:51 +00:00
"object_with_url_collection" : testPair {
expected : true ,
blank : & a . Object { } ,
result : & a . Object {
URL : a . ItemCollection {
a . IRI ( "http://littr.git/api/accounts/system" ) ,
a . IRI ( "http://littr.git/~system" ) ,
} ,
} ,
} ,
2018-06-09 11:08:30 +00:00
"object_simple" : testPair {
expected : true ,
2018-06-09 12:05:46 +00:00
blank : & a . Object { } ,
result : & a . Object {
2018-06-09 11:08:30 +00:00
Type : a . ObjectType ,
ID : a . ObjectID ( "http://www.test.example/object/1" ) ,
2018-09-02 13:41:53 +00:00
Name : a . NaturalLanguageValue { {
a . NilLangRef , "A Simple, non-specific object" ,
} } ,
2018-06-09 11:08:30 +00:00
} ,
} ,
2018-11-14 12:42:04 +00:00
"object_with_tags" : testPair {
expected : true ,
blank : & a . Object { } ,
result : & a . Object {
Type : a . ObjectType ,
ID : a . ObjectID ( "http://www.test.example/object/1" ) ,
Name : a . NaturalLanguageValue { {
a . NilLangRef , "A Simple, non-specific object" ,
} } ,
Tag : a . ItemCollection {
& a . Object {
Name : a . NaturalLanguageValue { {
a . NilLangRef , "#my_tag" ,
} } ,
ID : a . ObjectID ( "http://example.com/tag/my_tag" ) ,
} ,
& a . Mention {
Name : a . NaturalLanguageValue { {
a . NilLangRef , "@ana" ,
} } ,
Type : a . MentionType ,
ID : a . ObjectID ( "http://example.com/users/ana" ) ,
} ,
} ,
} ,
} ,
2018-12-02 15:50:40 +00:00
//"object_with_replies": testPair{
// expected: true,
// blank: &a.Object{},
// result: &a.Object{
// Type: a.ObjectType,
// ID: a.ObjectID("http://www.test.example/object/1"),
// Replies: &a.Collection{
// Parent: a.Parent{
// ID: a.ObjectID("http://www.test.example/object/1/replies"),
// Type: a.CollectionType,
// },
// TotalItems: 1,
// Items: a.ItemCollection{
// &a.Object{
// ID: a.ObjectID("http://www.test.example/object/1/replies/2"),
// Type: a.ArticleType,
// Name: a.NaturalLanguageValue{{a.NilLangRef, "Example title"}},
// },
// },
// },
// },
//},
2018-09-02 13:51:53 +00:00
"activity_simple" : testPair {
expected : true ,
blank : & a . Activity { } ,
result : & a . Activity {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
Type : a . ActivityType ,
Summary : a . NaturalLanguageValue { { a . NilLangRef , "Sally did something to a note" } } ,
} ,
2018-09-02 13:51:53 +00:00
Actor : & a . Person {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
Type : a . PersonType ,
Name : a . NaturalLanguageValue { { a . NilLangRef , "Sally" } } ,
} ,
2018-09-02 13:51:53 +00:00
} ,
Object : & a . Object {
Type : a . NoteType ,
2018-10-11 09:26:00 +00:00
Name : a . NaturalLanguageValue { { a . NilLangRef , "A Note" } } ,
2018-09-02 13:51:53 +00:00
} ,
} ,
} ,
2018-07-24 21:11:08 +00:00
"person_with_outbox" : testPair {
expected : true ,
blank : & a . Person { } ,
result : & a . Person {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
ID : a . ObjectID ( "http://example.com/accounts/ana" ) ,
Type : a . PersonType ,
Name : a . NaturalLanguageValue { { a . NilLangRef , "ana" } } ,
URL : a . IRI ( "http://example.com/accounts/ana" ) ,
} ,
2018-10-11 09:26:00 +00:00
PreferredUsername : a . NaturalLanguageValue { { a . NilLangRef , "Ana" } } ,
Outbox : & a . OrderedCollection {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
ID : a . ObjectID ( "http://example.com/accounts/ana/outbox" ) ,
Type : a . OrderedCollectionType ,
URL : a . IRI ( "http://example.com/outbox" ) ,
} ,
2018-07-24 21:11:08 +00:00
} ,
} ,
} ,
2018-08-05 11:54:01 +00:00
"ordered_collection" : testPair {
2018-08-06 11:06:14 +00:00
expected : true ,
2018-08-05 11:54:01 +00:00
blank : & a . OrderedCollection { } ,
result : & a . OrderedCollection {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
ID : a . ObjectID ( "http://example.com/outbox" ) ,
Type : a . OrderedCollectionType ,
URL : a . IRI ( "http://example.com/outbox" ) ,
} ,
2018-08-05 11:54:01 +00:00
TotalItems : 1 ,
OrderedItems : a . ItemCollection {
& a . Object {
2018-08-06 11:06:14 +00:00
ID : a . ObjectID ( "http://example.com/outbox/53c6fb47" ) ,
Type : a . ArticleType ,
2018-09-02 13:41:53 +00:00
Name : a . NaturalLanguageValue { { a . NilLangRef , "Example title" } } ,
Content : a . NaturalLanguageValue { { a . NilLangRef , "Example content!" } } ,
2018-10-11 18:13:34 +00:00
URL : a . IRI ( "http://example.com/53c6fb47" ) ,
2018-08-06 11:06:14 +00:00
MediaType : a . MimeType ( "text/markdown" ) ,
2018-08-06 17:11:24 +00:00
Published : time . Date ( 2018 , time . July , 5 , 16 , 46 , 44 , 0 , zLoc ) ,
2018-08-06 11:06:14 +00:00
Generator : a . IRI ( "http://example.com" ) ,
AttributedTo : a . IRI ( "http://example.com/accounts/alice" ) ,
2018-08-05 11:54:01 +00:00
} ,
} ,
} ,
} ,
2018-10-18 18:19:18 +00:00
"ordered_collection_page" : testPair {
expected : true ,
blank : & a . OrderedCollectionPage { } ,
result : & a . OrderedCollectionPage {
2018-12-02 15:50:40 +00:00
PartOf : a . IRI ( "http://example.com/outbox" ) ,
Next : a . IRI ( "http://example.com/outbox?page=3" ) ,
Prev : a . IRI ( "http://example.com/outbox?page=1" ) ,
2018-10-18 18:19:18 +00:00
OrderedCollection : a . OrderedCollection {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
ID : a . ObjectID ( "http://example.com/outbox?page=2" ) ,
Type : a . OrderedCollectionPageType ,
URL : a . IRI ( "http://example.com/outbox?page=2" ) ,
} ,
2018-12-02 15:50:40 +00:00
Current : a . IRI ( "http://example.com/outbox?page=2" ) ,
2018-10-18 18:19:18 +00:00
TotalItems : 1 ,
OrderedItems : a . ItemCollection {
& a . Object {
ID : a . ObjectID ( "http://example.com/outbox/53c6fb47" ) ,
Type : a . ArticleType ,
Name : a . NaturalLanguageValue { { a . NilLangRef , "Example title" } } ,
Content : a . NaturalLanguageValue { { a . NilLangRef , "Example content!" } } ,
URL : a . IRI ( "http://example.com/53c6fb47" ) ,
MediaType : a . MimeType ( "text/markdown" ) ,
Published : time . Date ( 2018 , time . July , 5 , 16 , 46 , 44 , 0 , zLoc ) ,
Generator : a . IRI ( "http://example.com" ) ,
AttributedTo : a . IRI ( "http://example.com/accounts/alice" ) ,
} ,
} ,
} ,
} ,
} ,
2018-08-31 11:38:15 +00:00
"natural_language_values" : {
expected : true ,
blank : & a . NaturalLanguageValue { } ,
result : & a . NaturalLanguageValue {
2018-09-02 13:41:53 +00:00
{
a . NilLangRef , `
2018-08-31 15:57:57 +00:00
2018-09-02 13:41:53 +00:00
` } ,
{ a . LangRef ( "en" ) , "Ana got apples ⓐ" } ,
{ a . LangRef ( "fr" ) , "Aná a des pommes ⒜" } ,
{ a . LangRef ( "ro" ) , "Ana are mere" } ,
2018-08-31 11:38:15 +00:00
} ,
} ,
2018-09-06 19:55:21 +00:00
"activity_create_simple" : {
2018-08-31 11:39:26 +00:00
expected : true ,
blank : & a . Create { } ,
result : & a . Create {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
Type : a . CreateType ,
} ,
2018-08-31 11:39:26 +00:00
Actor : a . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
Object : & a . Object {
Type : a . NoteType ,
AttributedTo : a . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
2018-08-31 15:57:57 +00:00
InReplyTo : a . IRI ( "https://littr.git/api/accounts/system/outbox/7ca154ff" ) ,
2018-09-02 13:41:53 +00:00
Content : a . NaturalLanguageValue { { a . NilLangRef , "<p>Hello world</p>" } } ,
2018-10-04 18:33:32 +00:00
To : a . ItemCollection { a . IRI ( "https://www.w3.org/ns/activitystreams#Public" ) } ,
2018-08-31 11:39:26 +00:00
} ,
} ,
} ,
2018-09-06 19:56:46 +00:00
"like_activity_with_iri_actor" : {
expected : true ,
2018-10-11 09:26:00 +00:00
blank : & ap . LikeActivity { } ,
result : & ap . LikeActivity {
2018-09-06 19:56:46 +00:00
Activity : & a . Like {
2018-11-23 14:22:56 +00:00
Parent : a . Parent {
Type : a . LikeType ,
} ,
2018-09-06 19:56:46 +00:00
Actor : a . IRI ( "https://littr.git/api/accounts/24d4b96f" ) ,
Object : & a . Object {
ID : a . ObjectID ( "https://littr.git/api/accounts/ana/liked/7ca154ff" ) ,
Type : a . ArticleType ,
} ,
} ,
Published : time . Date ( 2018 , time . September , 6 , 15 , 15 , 9 , 0 , zLoc ) ,
} ,
} ,
2018-06-07 14:48:08 +00:00
}
2018-07-24 21:11:08 +00:00
func getFileContents ( path string ) ( [ ] byte , error ) {
f , err := os . Open ( path )
if err != nil {
return nil , err
}
2018-06-07 14:48:08 +00:00
2018-08-06 11:06:14 +00:00
st , err := f . Stat ( )
if err != nil {
return nil , err
}
data := make ( [ ] byte , st . Size ( ) )
2018-06-07 14:48:08 +00:00
io . ReadFull ( f , data )
data = bytes . Trim ( data , "\x00" )
2018-07-24 21:11:08 +00:00
return data , nil
2018-06-07 14:48:08 +00:00
}
func Test_ActivityPubUnmarshall ( t * testing . T ) {
var err error
2018-08-31 11:38:15 +00:00
var f = t . Errorf
2018-06-07 14:48:08 +00:00
if len ( allTests ) == 0 {
t . Skip ( "No tests found" )
}
2018-06-09 11:08:30 +00:00
for k , pair := range allTests {
2018-07-24 21:11:08 +00:00
var data [ ] byte
2018-10-11 09:26:00 +00:00
path := filepath . Join ( dir , fmt . Sprintf ( "%s.json" , k ) )
data , err = getFileContents ( path )
2018-07-24 21:11:08 +00:00
if err != nil {
2018-10-11 09:26:00 +00:00
f ( "Error: %s for %s" , err , path )
2018-07-24 21:11:08 +00:00
continue
}
2018-06-07 14:48:08 +00:00
object := pair . blank
err = j . Unmarshal ( data , object )
if err != nil {
2018-07-24 21:11:08 +00:00
f ( "Error: %s for %s" , err , data )
2018-06-07 14:48:08 +00:00
continue
}
expLbl := ""
if ! pair . expected {
expLbl = "not be "
}
2018-08-06 11:06:14 +00:00
status := assertDeepEquals ( f , object , pair . result )
if pair . expected != status {
2018-08-05 11:54:01 +00:00
if stopOnFailure {
f = t . Fatalf
}
2018-12-02 15:50:40 +00:00
f ( "Mock: %s: %s\n%#v\n should %sequal to expected\n%#v" , k , path , object , expLbl , pair . result )
2018-08-06 11:06:14 +00:00
continue
}
if ! status {
oj , err := j . Marshal ( object )
if err != nil {
f ( err . Error ( ) )
2018-07-24 21:11:08 +00:00
}
2018-08-06 11:06:14 +00:00
tj , err := j . Marshal ( pair . result )
if err != nil {
f ( err . Error ( ) )
2018-07-24 21:11:08 +00:00
}
2018-12-02 15:50:40 +00:00
f ( "Mock: %s: %s\n%s\n should %sequal to expected\n%s" , k , path , oj , expLbl , tj )
2018-06-07 14:48:08 +00:00
}
if err == nil {
2018-10-11 09:26:00 +00:00
fmt . Printf ( " --- %s: %s\n %s\n" , "PASS" , k , path )
2018-06-07 14:48:08 +00:00
}
}
}