2019-12-03 19:45:26 +00:00
package tests
2019-12-03 15:33:20 +00:00
import (
"bytes"
"fmt"
2019-12-03 19:45:26 +00:00
pub "github.com/go-ap/activitypub"
2019-12-03 15:33:20 +00:00
"io"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"unsafe"
j "github.com/go-ap/jsonld"
)
const dir = "./mocks"
var stopOnFailure = false
type testPair struct {
expected bool
blank interface { }
result interface { }
}
2019-12-03 16:23:59 +00:00
type testMaps map [ string ] testPair
2019-12-03 15:33:20 +00:00
type visit struct {
a1 unsafe . Pointer
a2 unsafe . Pointer
typ reflect . Type
}
type canErrorFunc func ( format string , args ... interface { } )
func assertDeepEquals ( t canErrorFunc , x , y interface { } ) bool {
if x == nil || y == nil {
return x == y
}
v1 := reflect . ValueOf ( x )
//if v1.CanAddr() {
// v1 = v1.Addr()
//}
v2 := reflect . ValueOf ( y )
//if v2.CanAddr() {
// v2 = v2.Addr()
//}
if v1 . Type ( ) != v2 . Type ( ) {
t ( "%T != %T" , x , y )
return false
}
return deepValueEqual ( t , v1 , v2 , make ( map [ visit ] bool ) , 0 )
}
func deepValueEqual ( t canErrorFunc , v1 , v2 reflect . Value , visited map [ visit ] bool , depth int ) bool {
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 ( ) {
if v1 . IsNil ( ) == v2 . IsNil ( ) {
return true
}
var isNil1 , isNil2 string
if v1 . IsNil ( ) {
isNil1 = "is"
} else {
isNil1 = "is not"
}
if v2 . IsNil ( ) {
isNil2 = "is"
} else {
isNil2 = "is not"
}
t ( "Interface '%s' %s nil and '%s' %s nil" , v1 . Type ( ) . Name ( ) , isNil1 , v2 . Type ( ) . Name ( ) , isNil2 )
return false
}
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 ) {
t ( "Struct fields at pos %d %s[%s] and %s[%s] 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 ( ) )
if v1 . Field ( i ) . CanAddr ( ) && v2 . Field ( i ) . CanAddr ( ) {
t ( " Values: %#v - %#v" , v1 . Field ( i ) . Interface ( ) , v2 . Field ( i ) . Interface ( ) )
}
return false
}
}
return true
case reflect . Map :
if v1 . IsNil ( ) != v2 . IsNil ( ) {
t ( "Maps are not nil" , v1 . Type ( ) . Name ( ) , v2 . Type ( ) . Name ( ) )
return false
}
if v1 . Len ( ) != v2 . Len ( ) {
t ( "Maps don't have the same length %d vs %d" , v1 . Len ( ) , v2 . Len ( ) )
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 ) {
t ( "Maps values at index %s are not equal" , k . String ( ) )
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?
}
var zLoc , _ = time . LoadLocation ( "UTC" )
2019-12-03 16:23:59 +00:00
var allTests = testMaps {
2019-12-03 15:33:20 +00:00
"empty" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object { } ,
2019-12-03 15:33:20 +00:00
} ,
"link_simple" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Link { } ,
result : & pub . Link {
Type : pub . LinkType ,
Href : pub . IRI ( "http://example.org/abc" ) ,
HrefLang : pub . LangRef ( "en" ) ,
MediaType : pub . MimeType ( "text/html" ) ,
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "An example link" ,
2019-12-03 15:33:20 +00:00
} } ,
} ,
} ,
"object_with_url" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
URL : pub . IRI ( "http://littr.git/api/accounts/system" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
"object_with_url_collection" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
URL : pub . ItemCollection {
pub . IRI ( "http://littr.git/api/accounts/system" ) ,
pub . IRI ( "http://littr.git/~system" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
"object_simple" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
Type : pub . ObjectType ,
ID : pub . ObjectID ( "http://www.test.example/object/1" ) ,
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "A Simple, non-specific object" ,
2019-12-03 15:33:20 +00:00
} } ,
} ,
} ,
"object_with_tags" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
Type : pub . ObjectType ,
ID : pub . ObjectID ( "http://www.test.example/object/1" ) ,
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "A Simple, non-specific object" ,
2019-12-03 15:33:20 +00:00
} } ,
2019-12-03 19:45:26 +00:00
Tag : pub . ItemCollection {
& pub . Mention {
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "#my_tag" ,
2019-12-03 15:33:20 +00:00
} } ,
2019-12-03 19:45:26 +00:00
Type : pub . MentionType ,
ID : pub . ObjectID ( "http://example.com/tag/my_tag" ) ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
& pub . Mention {
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "@ana" ,
2019-12-03 15:33:20 +00:00
} } ,
2019-12-03 19:45:26 +00:00
Type : pub . MentionType ,
ID : pub . ObjectID ( "http://example.com/users/ana" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
} ,
"object_with_replies" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
Type : pub . ObjectType ,
ID : pub . ObjectID ( "http://www.test.example/object/1" ) ,
Replies : & pub . Collection {
ID : pub . ObjectID ( "http://www.test.example/object/1/replies" ) ,
Type : pub . CollectionType ,
2019-12-03 15:33:20 +00:00
TotalItems : 1 ,
2019-12-03 19:45:26 +00:00
Items : pub . ItemCollection {
& pub . Object {
ID : pub . ObjectID ( "http://www.test.example/object/1/replies/2" ) ,
Type : pub . ArticleType ,
Name : pub . NaturalLanguageValues { {
pub . NilLangRef , "Example title" ,
} } ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
} ,
} ,
"activity_simple" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Activity {
Actor : & pub . Person { } ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
result : & pub . Activity {
Type : pub . ActivityType ,
Summary : pub . NaturalLanguageValues { { pub . NilLangRef , "Sally did something to a note" } } ,
Actor : & pub . Person {
Type : pub . PersonType ,
Name : pub . NaturalLanguageValues { { pub . NilLangRef , "Sally" } } ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
Object : & pub . Object {
Type : pub . NoteType ,
Name : pub . NaturalLanguageValues { { pub . NilLangRef , "A Note" } } ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
"person_with_outbox" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Person { } ,
result : & pub . Person {
ID : pub . ObjectID ( "http://example.com/accounts/ana" ) ,
Type : pub . PersonType ,
Name : pub . NaturalLanguageValues { { pub . NilLangRef , "ana" } } ,
PreferredUsername : pub . NaturalLanguageValues { { pub . NilLangRef , "ana" } } ,
URL : pub . IRI ( "http://example.com/accounts/ana" ) ,
Outbox : & pub . OrderedCollection {
ID : "http://example.com/accounts/ana/outbox" ,
Type : pub . OrderedCollectionType ,
URL : pub . IRI ( "http://example.com/outbox" ) ,
2019-12-03 19:12:47 +00:00
} ,
2019-12-03 15:33:20 +00:00
} ,
} ,
"ordered_collection" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . OrderedCollection { } ,
result : & pub . OrderedCollection {
ID : pub . ObjectID ( "http://example.com/outbox" ) ,
Type : pub . OrderedCollectionType ,
URL : pub . IRI ( "http://example.com/outbox" ) ,
2019-12-03 15:33:20 +00:00
TotalItems : 1 ,
2019-12-03 19:45:26 +00:00
OrderedItems : pub . ItemCollection {
& pub . Object {
ID : pub . ObjectID ( "http://example.com/outbox/53c6fb47" ) ,
Type : pub . ArticleType ,
Name : pub . NaturalLanguageValues { { pub . NilLangRef , "Example title" } } ,
Content : pub . NaturalLanguageValues { { pub . NilLangRef , "Example content!" } } ,
URL : pub . IRI ( "http://example.com/53c6fb47" ) ,
MediaType : pub . MimeType ( "text/markdown" ) ,
2019-12-03 15:33:20 +00:00
Published : time . Date ( 2018 , time . July , 5 , 16 , 46 , 44 , 0 , zLoc ) ,
2019-12-03 19:45:26 +00:00
Generator : pub . IRI ( "http://example.com" ) ,
AttributedTo : pub . IRI ( "http://example.com/accounts/alice" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
} ,
"ordered_collection_page" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . OrderedCollectionPage { } ,
result : & pub . OrderedCollectionPage {
PartOf : pub . IRI ( "http://example.com/outbox" ) ,
Next : pub . IRI ( "http://example.com/outbox?page=3" ) ,
Prev : pub . IRI ( "http://example.com/outbox?page=1" ) ,
ID : pub . ObjectID ( "http://example.com/outbox?page=2" ) ,
Type : pub . OrderedCollectionPageType ,
URL : pub . IRI ( "http://example.com/outbox?page=2" ) ,
Current : pub . IRI ( "http://example.com/outbox?page=2" ) ,
2019-12-03 15:33:20 +00:00
TotalItems : 1 ,
2019-12-03 19:45:26 +00:00
OrderedItems : pub . ItemCollection {
& pub . Object {
ID : pub . ObjectID ( "http://example.com/outbox/53c6fb47" ) ,
Type : pub . ArticleType ,
Name : pub . NaturalLanguageValues { { pub . NilLangRef , "Example title" } } ,
Content : pub . NaturalLanguageValues { { pub . NilLangRef , "Example content!" } } ,
URL : pub . IRI ( "http://example.com/53c6fb47" ) ,
MediaType : pub . MimeType ( "text/markdown" ) ,
2019-12-03 15:33:20 +00:00
Published : time . Date ( 2018 , time . July , 5 , 16 , 46 , 44 , 0 , zLoc ) ,
2019-12-03 19:45:26 +00:00
Generator : pub . IRI ( "http://example.com" ) ,
AttributedTo : pub . IRI ( "http://example.com/accounts/alice" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
} ,
"natural_language_values" : {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . NaturalLanguageValues { } ,
result : & pub . NaturalLanguageValues {
2019-12-03 15:33:20 +00:00
{
2019-12-03 19:45:26 +00:00
pub . NilLangRef , `
2019-12-03 15:33:20 +00:00
` } ,
2019-12-03 19:45:26 +00:00
{ pub . LangRef ( "en" ) , "Ana got apples ⓐ" } ,
{ pub . LangRef ( "fr" ) , "Aná a des pommes ⒜" } ,
{ pub . LangRef ( "ro" ) , "Ana are mere" } ,
2019-12-03 15:33:20 +00:00
} ,
} ,
"activity_create_simple" : {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Create { } ,
result : & pub . Create {
Type : pub . CreateType ,
Actor : pub . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
Object : & pub . Object {
Type : pub . NoteType ,
AttributedTo : pub . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
InReplyTo : pub . ItemCollection { pub . IRI ( "https://littr.git/api/accounts/system/outbox/7ca154ff" ) } ,
Content : pub . NaturalLanguageValues { { pub . NilLangRef , "<p>Hello world</p>" } } ,
To : pub . ItemCollection { pub . IRI ( "https://www.w3.org/ns/activitystreams#Public" ) } ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
"activity_create_multiple_objects" : {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Create { } ,
result : & pub . Create {
Type : pub . CreateType ,
Actor : pub . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
Object : pub . ItemCollection {
& pub . Object {
Type : pub . NoteType ,
AttributedTo : pub . IRI ( "https://littr.git/api/accounts/anonymous" ) ,
InReplyTo : pub . ItemCollection { pub . IRI ( "https://littr.git/api/accounts/system/outbox/7ca154ff" ) } ,
Content : pub . NaturalLanguageValues { { pub . NilLangRef , "<p>Hello world</p>" } } ,
To : pub . ItemCollection { pub . IRI ( "https://www.w3.org/ns/activitystreams#Public" ) } ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
& pub . Article {
Type : pub . ArticleType ,
ID : pub . ObjectID ( "http://www.test.example/article/1" ) ,
Name : pub . NaturalLanguageValues {
2019-12-03 15:33:20 +00:00
{
2019-12-03 19:45:26 +00:00
pub . NilLangRef ,
2019-12-03 15:33:20 +00:00
"This someday will grow up to be an article" ,
} ,
} ,
2019-12-03 19:45:26 +00:00
InReplyTo : pub . ItemCollection {
pub . IRI ( "http://www.test.example/object/1" ) ,
pub . IRI ( "http://www.test.example/object/778" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
} ,
} ,
"object_with_audience" : testPair {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Object { } ,
result : & pub . Object {
Type : pub . ObjectType ,
ID : pub . ObjectID ( "http://www.test.example/object/1" ) ,
To : pub . ItemCollection {
pub . IRI ( "https://www.w3.org/ns/activitystreams#Public" ) ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
Bto : pub . ItemCollection {
pub . IRI ( "http://example.com/sharedInbox" ) ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
CC : pub . ItemCollection {
pub . IRI ( "https://example.com/actors/ana" ) ,
pub . IRI ( "https://example.com/actors/bob" ) ,
2019-12-03 15:33:20 +00:00
} ,
2019-12-03 19:45:26 +00:00
BCC : pub . ItemCollection {
pub . IRI ( "https://darkside.cookie/actors/darthvader" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
"article_with_multiple_inreplyto" : {
expected : true ,
2019-12-03 19:45:26 +00:00
blank : & pub . Article { } ,
result : & pub . Article {
Type : pub . ArticleType ,
ID : pub . ObjectID ( "http://www.test.example/article/1" ) ,
Name : pub . NaturalLanguageValues {
2019-12-03 15:33:20 +00:00
{
2019-12-03 19:45:26 +00:00
pub . NilLangRef ,
2019-12-03 15:33:20 +00:00
"This someday will grow up to be an article" ,
} ,
} ,
2019-12-03 19:45:26 +00:00
InReplyTo : pub . ItemCollection {
pub . IRI ( "http://www.test.example/object/1" ) ,
pub . IRI ( "http://www.test.example/object/778" ) ,
2019-12-03 15:33:20 +00:00
} ,
} ,
} ,
}
func getFileContents ( path string ) ( [ ] byte , error ) {
f , err := os . Open ( path )
if err != nil {
return nil , err
}
st , err := f . Stat ( )
if err != nil {
return nil , err
}
data := make ( [ ] byte , st . Size ( ) )
io . ReadFull ( f , data )
data = bytes . Trim ( data , "\x00" )
return data , nil
}
func TestUnmarshal ( t * testing . T ) {
var err error
var f = t . Errorf
if len ( allTests ) == 0 {
t . Skip ( "No tests found" )
}
for k , pair := range allTests {
path := filepath . Join ( dir , fmt . Sprintf ( "%s.json" , k ) )
t . Run ( path , func ( t * testing . T ) {
var data [ ] byte
data , err = getFileContents ( path )
if err != nil {
f ( "Error: %s for %s" , err , path )
return
}
object := pair . blank
err = j . Unmarshal ( data , object )
if err != nil {
f ( "Error: %s for %s" , err , data )
return
}
expLbl := ""
if ! pair . expected {
expLbl = "not be "
}
status := assertDeepEquals ( f , object , pair . result )
if pair . expected != status {
if stopOnFailure {
f = t . Fatalf
}
f ( "Mock: %s: %s\n%#v\n should %sequal to expected\n%#v" , k , path , object , expLbl , pair . result )
return
}
if ! status {
oj , err := j . Marshal ( object )
if err != nil {
f ( err . Error ( ) )
}
tj , err := j . Marshal ( pair . result )
if err != nil {
f ( err . Error ( ) )
}
f ( "Mock: %s: %s\n%s\n should %sequal to expected\n%s" , k , path , oj , expLbl , tj )
}
//if err == nil {
// fmt.Printf(" --- %s: %s\n %s\n", "PASS", k, path)
//}
} )
}
}