2018-06-07 14:48:08 +00:00
package tests
import (
"bytes"
"fmt"
"io"
"os"
"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-07-06 10:48:04 +00:00
a "github.com/mariusor/activitypub.go/activitypub"
j "github.com/mariusor/activitypub.go/jsonld"
2018-06-07 14:48:08 +00:00
)
const dir = "./mocks"
var stopOnFailure = false
type testPair struct {
path string
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 )
v2 := reflect . ValueOf ( y )
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-08-31 15:52:43 +00:00
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
path : "./mocks/empty.json" ,
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 {
path : "./mocks/link_simple.json" ,
expected : true ,
blank : & a . Link { } ,
result : & a . Link {
Type : a . LinkType ,
Href : a . URI ( "http://example.org/abc" ) ,
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 {
path : "./mocks/object_with_url.json" ,
expected : true ,
blank : & a . Object { } ,
result : & a . Object {
URL : a . URI ( "http://littr.git/api/accounts/system" ) ,
} ,
} ,
2018-06-09 11:08:30 +00:00
"object_simple" : testPair {
path : "./mocks/object_simple.json" ,
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-08-15 09:33:50 +00:00
"object_with_replies" : testPair {
path : "./mocks/object_with_replies.json" ,
expected : true ,
blank : & a . Object { } ,
result : & a . Object {
Type : a . ObjectType ,
ID : a . ObjectID ( "http://www.test.example/object/1" ) ,
Replies : & a . Collection {
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 ,
2018-09-02 13:41:53 +00:00
Name : a . NaturalLanguageValue { { a . NilLangRef , "Example title" } } ,
2018-08-15 09:33:50 +00:00
} ,
} ,
} ,
} ,
} ,
2018-09-02 13:51:53 +00:00
"activity_simple" : testPair {
path : "./mocks/activity_simple.json" ,
expected : true ,
blank : & a . Activity { } ,
result : & a . Activity {
Type : a . ActivityType ,
Summary : a . NaturalLanguageValue { { a . NilLangRef , "Sally did something to a note" } } ,
Actor : & a . Person {
Type : a . PersonType ,
Name : a . NaturalLanguageValue { {
a . NilLangRef , "Sally" ,
} } ,
} ,
Object : & a . Object {
Type : a . NoteType ,
Name : a . NaturalLanguageValue { {
a . NilLangRef , "A Note" ,
} } ,
} ,
} ,
} ,
2018-07-24 21:11:08 +00:00
"person_with_outbox" : testPair {
path : "./mocks/person_with_outbox.json" ,
expected : true ,
blank : & a . Person { } ,
result : & a . Person {
ID : a . ObjectID ( "http://example.com/accounts/ana" ) ,
Type : a . PersonType ,
2018-09-02 13:41:53 +00:00
Name : a . NaturalLanguageValue { {
a . NilLangRef , "ana" ,
} } ,
PreferredUsername : a . NaturalLanguageValue { {
a . NilLangRef , "Ana" ,
} } ,
2018-07-24 21:11:08 +00:00
URL : a . URI ( "http://example.com/accounts/ana" ) ,
2018-07-25 10:34:36 +00:00
Outbox : & a . OutboxStream {
2018-07-24 21:11:08 +00:00
ID : a . ObjectID ( "http://example.com/accounts/ana/outbox" ) ,
Type : a . OrderedCollectionType ,
URL : a . URI ( "http://example.com/outbox" ) ,
} ,
} ,
} ,
2018-08-05 11:54:01 +00:00
"ordered_collection" : testPair {
path : "./mocks/ordered_collection.json" ,
2018-08-06 11:06:14 +00:00
expected : true ,
2018-08-05 11:54:01 +00:00
blank : & a . OrderedCollection { } ,
result : & a . OrderedCollection {
ID : a . ObjectID ( "http://example.com/outbox" ) ,
Type : a . OrderedCollectionType ,
URL : a . URI ( "http://example.com/outbox" ) ,
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-08-06 17:11:24 +00:00
URL : a . URI ( "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-08-31 11:38:15 +00:00
"natural_language_values" : {
path : "./mocks/natural_language_values.json" ,
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-08-31 11:39:26 +00:00
"create_activity_simple" : {
path : "./mocks/create_activity_simple.json" ,
expected : true ,
blank : & a . Create { } ,
result : & a . Create {
Type : a . CreateType ,
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-08-31 11:39:26 +00:00
To : a . ObjectsArr { a . IRI ( "https://www.w3.org/ns/activitystreams#Public" ) } ,
} ,
} ,
} ,
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
data , err = getFileContents ( pair . path )
if err != nil {
f ( "Error: %s for %s" , err , pair . path )
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-08-15 09:33:50 +00:00
f ( "\n%#v\n should %sequal to expected\n%#v" , 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-08-05 11:54:01 +00:00
f ( "\n%s\n should %sequal to expected\n%s" , oj , expLbl , tj )
2018-06-07 14:48:08 +00:00
}
if err == nil {
2018-06-09 11:08:30 +00:00
fmt . Printf ( " --- %s: %s\n %s\n" , "PASS" , k , pair . path )
2018-06-07 14:48:08 +00:00
}
}
}