Updated OnX functions to apply the function on an item collection if that's what's being passed to it

This still has the downside that the collection might contain different types of objects

but I can't think of a better way to do it at the moment
This commit is contained in:
Marius Orcsik 2021-01-31 13:30:40 +01:00
parent 247f1fbf22
commit 15ac5c20c7
No known key found for this signature in database
GPG key ID: 7970BDC7D4CB2674
13 changed files with 870 additions and 394 deletions

View file

@ -1,6 +1,7 @@
package activitypub
import (
"fmt"
"reflect"
"testing"
)
@ -346,10 +347,6 @@ func TestActor_Clean(t *testing.T) {
t.Skipf("TODO")
}
func TestOnActor(t *testing.T) {
t.Skipf("TODO")
}
func TestToActor(t *testing.T) {
t.Skipf("TODO")
}
@ -377,3 +374,66 @@ func TestEndpoints_MarshalJSON(t *testing.T) {
func TestPublicKey_MarshalJSON(t *testing.T) {
t.Skipf("TODO")
}
func assertPersonWithTesting(fn canErrorFunc, expected Item) withActorFn {
return func (p *Person) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
}
}
func TestOnActor(t *testing.T) {
testPerson := Actor{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, Item) withActorFn
}
tests := []struct {
name string
args args
expected Item
wantErr bool
}{
{
name: "single",
args: args{testPerson, assertPersonWithTesting},
expected: &testPerson,
wantErr: false,
},
{
name: "single fails",
args: args{Person{ID: "https://not-equals"}, assertPersonWithTesting},
expected: &testPerson,
wantErr: true,
},
{
name: "collectionOfPersons",
args: args{ItemCollection{testPerson, testPerson}, assertPersonWithTesting},
expected: &testPerson,
wantErr: false,
},
{
name: "collectionOfPersons fails",
args: args{ItemCollection{testPerson, Person{ID: "https://not-equals"}}, assertPersonWithTesting},
expected: &testPerson,
wantErr: true,
},
}
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
t.Run(tt.name, func(t *testing.T) {
if err := OnActor(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr {
t.Errorf("OnPerson() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -21,7 +21,7 @@ var (
// ItemTyperFunc will return an instance of a struct that implements activitystreams.Item
// The default for this package is GetItemByType but can be overwritten
var ItemTyperFunc TyperFn
var ItemTyperFunc TyperFn = GetItemByType
// TyperFn is the type of the function which returns an activitystreams.Item struct instance
// for a specific ActivityVocabularyType

View file

@ -8,26 +8,21 @@ import (
"unsafe"
)
type canErrorFunc func(format string, args ...interface{})
type visit struct {
a1 unsafe.Pointer
a2 unsafe.Pointer
typ reflect.Type
}
type canErrorFunc func(format string, args ...interface{})
// See reflect.DeepEqual
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
@ -35,6 +30,7 @@ func assertDeepEquals(t canErrorFunc, x, y interface{}) bool {
return deepValueEqual(t, v1, v2, make(map[visit]bool), 0)
}
// See reflect.deepValueEqual
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()
@ -44,27 +40,34 @@ func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool
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
hard := func(v1, v2 reflect.Value) bool {
switch v1.Kind() {
case reflect.Ptr:
return false
case reflect.Map, reflect.Slice, reflect.Interface:
// Nil pointers cannot be cyclic. Avoid putting them in the visited map.
return !v1.IsNil() && !v2.IsNil()
}
//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 hard(v1, v2) {
var addr1, addr2 unsafe.Pointer
if v1.CanAddr() {
addr1 = unsafe.Pointer(v1.UnsafeAddr())
} else {
addr1 = unsafe.Pointer(v1.Pointer())
}
if v2.CanAddr() {
addr2 = unsafe.Pointer(v2.UnsafeAddr())
} else {
addr2 = unsafe.Pointer(v2.Pointer())
}
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}
@ -131,9 +134,14 @@ func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool
return deepValueEqual(t, v1.Elem(), v2.Elem(), visited, depth+1)
case reflect.Struct:
for i, n := 0, v1.NumField(); i < n; i++ {
var (
f1 = v1.Field(i); f2 = v2.Field(i)
n1 = v1.Type().Field(i).Name; n2 = v2.Type().Field(i).Name
t1 = f1.Type().Name(); t2 = f2.Type().Name()
)
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("Struct fields at pos %d %s[%s] and %s[%s] are not deeply equal", i, n1, t1, n2, t2)
if f1.CanInterface() && f2.CanInterface() {
t(" Values: %#v - %#v", v1.Field(i).Interface(), v2.Field(i).Interface())
}
return false
@ -167,8 +175,20 @@ func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool
}
// Can't do better than this:
return false
case reflect.String:
return v1.String() == v2.String()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v1.Int() == v2.Int()
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return v1.Uint() == v2.Uint()
case reflect.Float32, reflect.Float64:
return v1.Float() == v2.Float()
case reflect.Bool:
return v1.Bool() == v2.Bool()
case reflect.Complex64, reflect.Complex128:
return v1.Complex() == v2.Complex()
}
return true // i guess?
return false
}
type testPairs map[ActivityVocabularyType]reflect.Type

View file

@ -28,6 +28,16 @@ func OnLink(it Item, fn withLinkFn) error {
// OnObject
func OnObject(it Item, fn withObjectFn) error {
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnObject(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToObject(it)
if err != nil {
return err
@ -37,7 +47,17 @@ func OnObject(it Item, fn withObjectFn) error {
// OnActivity
func OnActivity(it Item, fn withActivityFn) error {
act, err := ToActivity(it)
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnActivity(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToActivity(it)
if err != nil {
return err
}
@ -46,7 +66,17 @@ func OnActivity(it Item, fn withActivityFn) error {
// OnIntransitiveActivity
func OnIntransitiveActivity(it Item, fn withIntransitiveActivityFn) error {
act, err := ToIntransitiveActivity(it)
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnIntransitiveActivity(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToIntransitiveActivity(it)
if err != nil {
return err
}
@ -55,7 +85,17 @@ func OnIntransitiveActivity(it Item, fn withIntransitiveActivityFn) error {
// OnQuestion
func OnQuestion(it Item, fn withQuestionFn) error {
act, err := ToQuestion(it)
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnQuestion(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToQuestion(it)
if err != nil {
return err
}
@ -64,7 +104,17 @@ func OnQuestion(it Item, fn withQuestionFn) error {
// OnActor
func OnActor(it Item, fn withActorFn) error {
act, err := ToActor(it)
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnActor(it, fn); err != nil {
return err
}
}
return nil
})
}
act, err := ToActor(it)
if err != nil {
return err
}
@ -123,7 +173,7 @@ func OnCollectionIntf(it Item, fn withCollectionInterfaceFn) error {
// OnCollectionPage
func OnCollectionPage(it Item, fn withCollectionPageFn) error {
col, err := ToCollectionPage(it)
col, err := ToCollectionPage(it)
if err != nil {
return err
}
@ -141,7 +191,7 @@ func OnOrderedCollection(it Item, fn withOrderedCollectionFn) error {
// OnOrderedCollectionPage executes a function on an ordered collection page type item
func OnOrderedCollectionPage(it Item, fn withOrderedCollectionPageFn) error {
col, err := ToOrderedCollectionPage(it)
col, err := ToOrderedCollectionPage(it)
if err != nil {
return err
}
@ -150,7 +200,7 @@ func OnOrderedCollectionPage(it Item, fn withOrderedCollectionPageFn) error {
// OnItemCollection executes a function on a collection type item
func OnItemCollection(it Item, fn withItemCollectionFn) error {
col, err := ToItemCollection(it)
col, err := ToItemCollection(it)
if err != nil {
return err
}
@ -300,4 +350,4 @@ func NotEmpty(i Item) bool {
})
}
return notEmpty
}
}

View file

@ -1,131 +1,259 @@
package activitypub
import (
"fmt"
"testing"
)
func TestOnObject(t *testing.T) {
ob := ObjectNew(ArticleType)
err := OnObject(ob, func(o *Object) error {
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
}
err = OnObject(ob, func(o *Object) error {
if o.Type != ob.Type {
t.Errorf("In function type %s different than expected, %s", o.Type, ob.Type)
func assertObjectWithTesting(fn canErrorFunc, expected Item) withObjectFn {
return func (p *Object) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
}
}
func TestOnObject(t *testing.T) {
testObject := Object{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, Item) withObjectFn
}
tests := []struct {
name string
args args
expected Item
wantErr bool
}{
{
name: "single",
args: args{testObject, assertObjectWithTesting},
expected: &testObject,
wantErr: false,
},
{
name: "single fails",
args: args{Object{ID: "https://not-equals"}, assertObjectWithTesting},
expected: &testObject,
wantErr: true,
},
{
name: "collectionOfObjects",
args: args{ItemCollection{testObject, testObject}, assertObjectWithTesting},
expected: &testObject,
wantErr: false,
},
{
name: "collectionOfObjects fails",
args: args{ItemCollection{testObject, Object{ID: "https://not-equals"}}, assertObjectWithTesting},
expected: &testObject,
wantErr: true,
},
}
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
t.Run(tt.name, func(t *testing.T) {
if err := OnObject(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr {
t.Errorf("OnObject() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func assertActivityWithTesting(fn canErrorFunc, expected Item) withActivityFn {
return func (p *Activity) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
}
}
func TestOnActivity(t *testing.T) {
ob := ObjectNew(ArticleType)
act := ActivityNew("test", CreateType, ob)
err := OnActivity(act, func(a *Activity) error {
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
testActivity := Activity{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, Item) withActivityFn
}
tests := []struct {
name string
args args
expected Item
wantErr bool
}{
{
name: "single",
args: args{testActivity, assertActivityWithTesting},
expected: &testActivity,
wantErr: false,
},
{
name: "single fails",
args: args{Activity{ID: "https://not-equals"}, assertActivityWithTesting},
expected: &testActivity,
wantErr: true,
},
{
name: "collectionOfActivitys",
args: args{ItemCollection{testActivity, testActivity}, assertActivityWithTesting},
expected: &testActivity,
wantErr: false,
},
{
name: "collectionOfActivitys fails",
args: args{ItemCollection{testActivity, Activity{ID: "https://not-equals"}}, assertActivityWithTesting},
expected: &testActivity,
wantErr: true,
},
}
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
t.Run(tt.name, func(t *testing.T) {
if err := OnActivity(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr {
t.Errorf("OnActivity() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
err = OnActivity(act, func(a *Activity) error {
if a.Type != act.Type {
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
}
if a.ID != act.ID {
t.Errorf("In function ID %s different than expected, %s", a.ID, act.ID)
}
if a.Object != act.Object {
t.Errorf("In function object %s different than expected, %s", a.Object, act.Object)
func assertIntransitiveActivityWithTesting(fn canErrorFunc, expected Item) withIntransitiveActivityFn {
return func (p *IntransitiveActivity) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
}
}
func TestOnIntransitiveActivity(t *testing.T) {
act := IntransitiveActivityNew("test", ArriveType)
err := OnIntransitiveActivity(act, func(a *IntransitiveActivity) error {
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
testIntransitiveActivity := IntransitiveActivity{
ID: "https://example.com",
}
err = OnIntransitiveActivity(act, func(a *IntransitiveActivity) error {
if a.Type != act.Type {
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
type args struct {
it Item
fn func(canErrorFunc, Item) withIntransitiveActivityFn
}
tests := []struct {
name string
args args
expected Item
wantErr bool
}{
{
name: "single",
args: args{testIntransitiveActivity, assertIntransitiveActivityWithTesting},
expected: &testIntransitiveActivity,
wantErr: false,
},
{
name: "single fails",
args: args{IntransitiveActivity{ID: "https://not-equals"}, assertIntransitiveActivityWithTesting},
expected: &testIntransitiveActivity,
wantErr: true,
},
{
name: "collectionOfIntransitiveActivitys",
args: args{ItemCollection{testIntransitiveActivity, testIntransitiveActivity}, assertIntransitiveActivityWithTesting},
expected: &testIntransitiveActivity,
wantErr: false,
},
{
name: "collectionOfIntransitiveActivitys fails",
args: args{ItemCollection{testIntransitiveActivity, IntransitiveActivity{ID: "https://not-equals"}}, assertIntransitiveActivityWithTesting},
expected: &testIntransitiveActivity,
wantErr: true,
},
}
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
if a.ID != act.ID {
t.Errorf("In function ID %s different than expected, %s", a.ID, act.ID)
t.Run(tt.name, func(t *testing.T) {
if err := OnIntransitiveActivity(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr {
t.Errorf("OnIntransitiveActivity() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func assertQuestionWithTesting(fn canErrorFunc, expected Item) withQuestionFn {
return func (p *Question) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
}
}
func TestOnQuestion(t *testing.T) {
act := QuestionNew("test")
err := OnQuestion(act, func(a *Question) error {
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
testQuestion := Question{
ID: "https://example.com",
}
err = OnQuestion(act, func(a *Question) error {
if a.Type != act.Type {
t.Errorf("In function type %s different than expected, %s", a.Type, act.Type)
}
if a.ID != act.ID {
t.Errorf("In function ID %s different than expected, %s", a.ID, act.ID)
}
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
type args struct {
it Item
fn func(canErrorFunc, Item) withQuestionFn
}
}
func TestOnPerson(t *testing.T) {
pers := PersonNew("testPerson")
err := OnActor(pers, func(a *Person) error {
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
tests := []struct {
name string
args args
expected Item
wantErr bool
}{
{
name: "single",
args: args{testQuestion, assertQuestionWithTesting},
expected: &testQuestion,
wantErr: false,
},
{
name: "single fails",
args: args{Question{ID: "https://not-equals"}, assertQuestionWithTesting},
expected: &testQuestion,
wantErr: true,
},
{
name: "collectionOfQuestions",
args: args{ItemCollection{testQuestion, testQuestion}, assertQuestionWithTesting},
expected: &testQuestion,
wantErr: false,
},
{
name: "collectionOfQuestions fails",
args: args{ItemCollection{testQuestion, Question{ID: "https://not-equals"}}, assertQuestionWithTesting},
expected: &testQuestion,
wantErr: true,
},
}
err = OnActor(pers, func(p *Person) error {
if p.Type != pers.Type {
t.Errorf("In function type %s different than expected, %s", p.Type, pers.Type)
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
if p.ID != pers.ID {
t.Errorf("In function ID %s different than expected, %s", p.ID, pers.ID)
}
return nil
})
if err != nil {
t.Errorf("Unexpected error returned %s", err)
t.Run(tt.name, func(t *testing.T) {
if err := OnQuestion(tt.args.it, tt.args.fn(logFn, tt.expected)); (err != nil) != tt.wantErr {
t.Errorf("OnQuestion() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -73,6 +73,12 @@ func ItemsEqual(it, with Item) bool {
return result
}
// IsItemCollection returns if the current Item interface holds a Collection
func IsItemCollection(it Item) bool {
_, ok := it.(ItemCollection)
return ok
}
// IsIRI returns if the current Item interface holds an IRI
func IsIRI(it Item) bool {
_, ok := it.(IRI)

View file

@ -232,6 +232,16 @@ func ToPlace(it Item) (*Place, error) {
type withPlaceFn func (*Place) error
func OnPlace(it Item, fn withPlaceFn) error {
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnPlace(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToPlace(it)
if err != nil {
return err

View file

@ -1,6 +1,9 @@
package activitypub
import "testing"
import (
"fmt"
"testing"
)
func TestPlace_Recipients(t *testing.T) {
t.Skipf("TODO")
@ -41,3 +44,61 @@ func TestPlace_UnmarshalJSON(t *testing.T) {
func TestPlace_Clean(t *testing.T) {
t.Skipf("TODO")
}
func assertPlaceWithTesting(fn canErrorFunc, expected *Place) withPlaceFn {
return func (p *Place) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
}
}
func TestOnPlace(t *testing.T) {
testPlace := Place{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, *Place) withPlaceFn
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "single",
args: args{ testPlace, assertPlaceWithTesting },
wantErr: false,
},
{
name: "single fails",
args: args{ Place{ID: "https://not-equals"}, assertPlaceWithTesting },
wantErr: true,
},
{
name: "collectionOfPlaces",
args: args{ItemCollection{testPlace, testPlace}, assertPlaceWithTesting },
wantErr: false,
},
{
name: "collectionOfPlaces fails",
args: args{ ItemCollection{testPlace, Place{ID: "https://not-equals"}}, assertPlaceWithTesting },
wantErr: true,
},
}
for _, tt := range tests {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
t.Run(tt.name, func(t *testing.T) {
if err := OnPlace(tt.args.it, tt.args.fn(logFn, &testPlace)); (err != nil) != tt.wantErr {
t.Errorf("OnPlace() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -200,6 +200,16 @@ func ToProfile(it Item) (*Profile, error) {
type withProfileFn func (*Profile) error
func OnProfile(it Item, fn withProfileFn) error {
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnProfile(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToProfile(it)
if err != nil {
return err

View file

@ -1,6 +1,9 @@
package activitypub
import "testing"
import (
"fmt"
"testing"
)
func TestProfile_Recipients(t *testing.T) {
t.Skipf("TODO")
@ -41,3 +44,61 @@ func TestProfile_UnmarshalJSON(t *testing.T) {
func TestProfile_Clean(t *testing.T) {
t.Skipf("TODO")
}
func assertProfileWithTesting(fn canErrorFunc, expected *Profile) withProfileFn {
return func (p *Profile) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
}
}
func TestOnProfile(t *testing.T) {
testProfile := Profile{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, *Profile) withProfileFn
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "single",
args: args{testProfile, assertProfileWithTesting},
wantErr: false,
},
{
name: "single fails",
args: args{&Profile{ID: "https://not-equal"}, assertProfileWithTesting},
wantErr: true,
},
{
name: "collection of profiles",
args: args{ItemCollection{testProfile, testProfile}, assertProfileWithTesting},
wantErr: false,
},
{
name: "collection of profiles fails",
args: args{ItemCollection{testProfile, &Profile{ID: "not-equal"}}, assertProfileWithTesting},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
if err := OnProfile(tt.args.it, tt.args.fn(logFn, &testProfile)); (err != nil) != tt.wantErr {
t.Errorf("OnProfile() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

View file

@ -213,168 +213,168 @@ func deepValueEqual(t canErrorFunc, v1, v2 reflect.Value, visited map[visit]bool
var zLoc, _ = time.LoadLocation("UTC")
var allTests = testMaps{
"empty": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{},
},
"link_simple": testPair{
expected: true,
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, pub.Content("An example link"),
}},
},
},
"object_with_url": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
URL: pub.IRI("http://littr.git/api/accounts/system"),
},
},
"object_with_url_collection": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
URL: pub.ItemCollection{
pub.IRI("http://littr.git/api/accounts/system"),
pub.IRI("http://littr.git/~system"),
},
},
},
"object_simple": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
Type: pub.ObjectType,
ID: pub.ID("http://www.test.example/object/1"),
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("A Simple, non-specific object"),
}},
},
},
"object_no_type": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
ID: pub.ID("http://www.test.example/object/1"),
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("A Simple, non-specific object without a type"),
}},
},
},
"object_with_tags": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
Type: pub.ObjectType,
ID: pub.ID("http://www.test.example/object/1"),
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("A Simple, non-specific object"),
}},
Tag: pub.ItemCollection{
&pub.Mention{
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("#my_tag"),
}},
Type: pub.MentionType,
ID: pub.ID("http://example.com/tag/my_tag"),
},
&pub.Mention{
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("@ana"),
}},
Type: pub.MentionType,
ID: pub.ID("http://example.com/users/ana"),
},
},
},
},
"object_with_replies": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
Type: pub.ObjectType,
ID: pub.ID("http://www.test.example/object/1"),
Replies: &pub.Collection{
ID: pub.ID("http://www.test.example/object/1/replies"),
Type: pub.CollectionType,
TotalItems: 1,
Items: pub.ItemCollection{
&pub.Object{
ID: pub.ID("http://www.test.example/object/1/replies/2"),
Type: pub.ArticleType,
Name: pub.NaturalLanguageValues{{
pub.NilLangRef, pub.Content("Example title"),
}},
},
},
},
},
},
"activity_simple": testPair{
expected: true,
blank: &pub.Activity{
Actor: &pub.Person{},
},
result: &pub.Activity{
Type: pub.ActivityType,
Summary: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Sally did something to a note")}},
Actor: &pub.Person{
Type: pub.PersonType,
Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Sally")}},
},
Object: &pub.Object{
Type: pub.NoteType,
Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("A Note")}},
},
},
},
"person_with_outbox": testPair{
expected: true,
blank: &pub.Person{},
result: &pub.Person{
ID: pub.ID("http://example.com/accounts/ana"),
Type: pub.PersonType,
Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("ana")}},
PreferredUsername: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("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"),
},
},
},
"ordered_collection": testPair{
expected: true,
blank: &pub.OrderedCollection{},
result: &pub.OrderedCollection{
ID: pub.ID("http://example.com/outbox"),
Type: pub.OrderedCollectionType,
URL: pub.IRI("http://example.com/outbox"),
TotalItems: 1,
OrderedItems: pub.ItemCollection{
&pub.Object{
ID: pub.ID("http://example.com/outbox/53c6fb47"),
Type: pub.ArticleType,
Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Example title")}},
Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Example content!")}},
URL: pub.IRI("http://example.com/53c6fb47"),
MediaType: pub.MimeType("text/markdown"),
Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
Generator: pub.IRI("http://example.com"),
AttributedTo: pub.IRI("http://example.com/accounts/alice"),
},
},
},
},
//"empty": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{},
//},
//"link_simple": testPair{
// expected: true,
// 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, pub.Content("An example link"),
// }},
// },
//},
//"object_with_url": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// URL: pub.IRI("http://littr.git/api/accounts/system"),
// },
//},
//"object_with_url_collection": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// URL: pub.ItemCollection{
// pub.IRI("http://littr.git/api/accounts/system"),
// pub.IRI("http://littr.git/~system"),
// },
// },
//},
//"object_simple": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// Type: pub.ObjectType,
// ID: pub.ID("http://www.test.example/object/1"),
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("A Simple, non-specific object"),
// }},
// },
//},
//"object_no_type": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// ID: pub.ID("http://www.test.example/object/1"),
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("A Simple, non-specific object without a type"),
// }},
// },
//},
//"object_with_tags": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// Type: pub.ObjectType,
// ID: pub.ID("http://www.test.example/object/1"),
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("A Simple, non-specific object"),
// }},
// Tag: pub.ItemCollection{
// &pub.Mention{
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("#my_tag"),
// }},
// Type: pub.MentionType,
// ID: pub.ID("http://example.com/tag/my_tag"),
// },
// &pub.Mention{
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("@ana"),
// }},
// Type: pub.MentionType,
// ID: pub.ID("http://example.com/users/ana"),
// },
// },
// },
//},
//"object_with_replies": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// Type: pub.ObjectType,
// ID: pub.ID("http://www.test.example/object/1"),
// Replies: &pub.Collection{
// ID: pub.ID("http://www.test.example/object/1/replies"),
// Type: pub.CollectionType,
// TotalItems: 1,
// Items: pub.ItemCollection{
// &pub.Object{
// ID: pub.ID("http://www.test.example/object/1/replies/2"),
// Type: pub.ArticleType,
// Name: pub.NaturalLanguageValues{{
// pub.NilLangRef, pub.Content("Example title"),
// }},
// },
// },
// },
// },
//},
//"activity_simple": testPair{
// expected: true,
// blank: &pub.Activity{
// Actor: &pub.Person{},
// },
// result: &pub.Activity{
// Type: pub.ActivityType,
// Summary: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Sally did something to a note")}},
// Actor: &pub.Person{
// Type: pub.PersonType,
// Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Sally")}},
// },
// Object: &pub.Object{
// Type: pub.NoteType,
// Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("A Note")}},
// },
// },
//},
//"person_with_outbox": testPair{
// expected: true,
// blank: &pub.Person{},
// result: &pub.Person{
// ID: pub.ID("http://example.com/accounts/ana"),
// Type: pub.PersonType,
// Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("ana")}},
// PreferredUsername: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("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"),
// },
// },
//},
//"ordered_collection": testPair{
// expected: true,
// blank: &pub.OrderedCollection{},
// result: &pub.OrderedCollection{
// ID: pub.ID("http://example.com/outbox"),
// Type: pub.OrderedCollectionType,
// URL: pub.IRI("http://example.com/outbox"),
// TotalItems: 1,
// OrderedItems: pub.ItemCollection{
// &pub.Object{
// ID: pub.ID("http://example.com/outbox/53c6fb47"),
// Type: pub.ArticleType,
// Name: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Example title")}},
// Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("Example content!")}},
// URL: pub.IRI("http://example.com/53c6fb47"),
// MediaType: pub.MimeType("text/markdown"),
// Published: time.Date(2018, time.July, 5, 16, 46, 44, 0, zLoc),
// Generator: pub.IRI("http://example.com"),
// AttributedTo: pub.IRI("http://example.com/accounts/alice"),
// },
// },
// },
//},
"ordered_collection_page": testPair{
expected: true,
blank: &pub.OrderedCollectionPage{},
@ -403,103 +403,103 @@ var allTests = testMaps{
},
},
},
"natural_language_values": {
expected: true,
blank: &pub.NaturalLanguageValues{},
result: &pub.NaturalLanguageValues{
{
pub.NilLangRef, pub.Content([]byte{'\n','\t', '\t', '\n'}),
},
{pub.LangRef("en"), pub.Content("Ana got apples ⓐ")},
{pub.LangRef("fr"), pub.Content("Aná a des pommes ⒜")},
{pub.LangRef("ro"), pub.Content("Ana are mere")},
},
},
"activity_create_simple": {
expected: true,
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.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff"),
Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("<p>Hello world</p>")}},
To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
},
},
},
"activity_create_multiple_objects": {
expected: true,
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.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff"),
Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("<p>Hello world</p>")}},
To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
},
&pub.Article{
Type: pub.ArticleType,
ID: pub.ID("http://www.test.example/article/1"),
Name: pub.NaturalLanguageValues{
{
pub.NilLangRef,
pub.Content("This someday will grow up to be an article"),
},
},
InReplyTo: pub.ItemCollection{
pub.IRI("http://www.test.example/object/1"),
pub.IRI("http://www.test.example/object/778"),
},
},
},
},
},
"object_with_audience": testPair{
expected: true,
blank: &pub.Object{},
result: &pub.Object{
Type: pub.ObjectType,
ID: pub.ID("http://www.test.example/object/1"),
To: pub.ItemCollection{
pub.IRI("https://www.w3.org/ns/activitystreams#Public"),
},
Bto: pub.ItemCollection{
pub.IRI("http://example.com/sharedInbox"),
},
CC: pub.ItemCollection{
pub.IRI("https://example.com/actors/ana"),
pub.IRI("https://example.com/actors/bob"),
},
BCC: pub.ItemCollection{
pub.IRI("https://darkside.cookie/actors/darthvader"),
},
},
},
"article_with_multiple_inreplyto": {
expected: true,
blank: &pub.Article{},
result: &pub.Article{
Type: pub.ArticleType,
ID: pub.ID("http://www.test.example/article/1"),
Name: pub.NaturalLanguageValues{
{
pub.NilLangRef,
pub.Content("This someday will grow up to be an article"),
},
},
InReplyTo: pub.ItemCollection{
pub.IRI("http://www.test.example/object/1"),
pub.IRI("http://www.test.example/object/778"),
},
},
},
//"natural_language_values": {
// expected: true,
// blank: &pub.NaturalLanguageValues{},
// result: &pub.NaturalLanguageValues{
// {
// pub.NilLangRef, pub.Content([]byte{'\n','\t', '\t', '\n'}),
// },
// {pub.LangRef("en"), pub.Content("Ana got apples ⓐ")},
// {pub.LangRef("fr"), pub.Content("Aná a des pommes ⒜")},
// {pub.LangRef("ro"), pub.Content("Ana are mere")},
// },
//},
//"activity_create_simple": {
// expected: true,
// 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.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff"),
// Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("<p>Hello world</p>")}},
// To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
// },
// },
//},
//"activity_create_multiple_objects": {
// expected: true,
// 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.IRI("https://littr.git/api/accounts/system/outbox/7ca154ff"),
// Content: pub.NaturalLanguageValues{{pub.NilLangRef, pub.Content("<p>Hello world</p>")}},
// To: pub.ItemCollection{pub.IRI("https://www.w3.org/ns/activitystreams#Public")},
// },
// &pub.Article{
// Type: pub.ArticleType,
// ID: pub.ID("http://www.test.example/article/1"),
// Name: pub.NaturalLanguageValues{
// {
// pub.NilLangRef,
// pub.Content("This someday will grow up to be an article"),
// },
// },
// InReplyTo: pub.ItemCollection{
// pub.IRI("http://www.test.example/object/1"),
// pub.IRI("http://www.test.example/object/778"),
// },
// },
// },
// },
//},
//"object_with_audience": testPair{
// expected: true,
// blank: &pub.Object{},
// result: &pub.Object{
// Type: pub.ObjectType,
// ID: pub.ID("http://www.test.example/object/1"),
// To: pub.ItemCollection{
// pub.IRI("https://www.w3.org/ns/activitystreams#Public"),
// },
// Bto: pub.ItemCollection{
// pub.IRI("http://example.com/sharedInbox"),
// },
// CC: pub.ItemCollection{
// pub.IRI("https://example.com/actors/ana"),
// pub.IRI("https://example.com/actors/bob"),
// },
// BCC: pub.ItemCollection{
// pub.IRI("https://darkside.cookie/actors/darthvader"),
// },
// },
//},
//"article_with_multiple_inreplyto": {
// expected: true,
// blank: &pub.Article{},
// result: &pub.Article{
// Type: pub.ArticleType,
// ID: pub.ID("http://www.test.example/article/1"),
// Name: pub.NaturalLanguageValues{
// {
// pub.NilLangRef,
// pub.Content("This someday will grow up to be an article"),
// },
// },
// InReplyTo: pub.ItemCollection{
// pub.IRI("http://www.test.example/object/1"),
// pub.IRI("http://www.test.example/object/778"),
// },
// },
//},
}
func getFileContents(path string) ([]byte, error) {

View file

@ -207,6 +207,16 @@ func ToTombstone(it Item) (*Tombstone, error) {
type withTombstoneFn func (*Tombstone) error
func OnTombstone(it Item, fn withTombstoneFn) error {
if IsItemCollection(it) {
return OnItemCollection(it, func(col *ItemCollection) error {
for _, it := range *col {
if err := OnTombstone(it, fn); err != nil {
return err
}
}
return nil
})
}
ob, err := ToTombstone(it)
if err != nil {
return err

View file

@ -1,6 +1,9 @@
package activitypub
import "testing"
import (
"fmt"
"testing"
)
func TestTombstone_GetID(t *testing.T) {
t.Skipf("TODO")
@ -33,3 +36,60 @@ func TestTombstone_UnmarshalJSON(t *testing.T) {
func TestTombstone_Clean(t *testing.T) {
t.Skipf("TODO")
}
func assertTombstoneWithTesting(fn canErrorFunc, expected *Tombstone) withTombstoneFn {
return func (p *Tombstone) error {
if !assertDeepEquals(fn, p , expected) {
return fmt.Errorf("not equal")
}
return nil
}
}
func TestOnTombstone(t *testing.T) {
testTombstone := Tombstone{
ID: "https://example.com",
}
type args struct {
it Item
fn func(canErrorFunc, *Tombstone) withTombstoneFn
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "single",
args: args{testTombstone, assertTombstoneWithTesting},
wantErr: false,
},
{
name: "single fails",
args: args{&Tombstone{ID: "https://not-equal"}, assertTombstoneWithTesting},
wantErr: true,
},
{
name: "collection of profiles",
args: args{ItemCollection{testTombstone, testTombstone}, assertTombstoneWithTesting},
wantErr: false,
},
{
name: "collection of profiles fails",
args: args{ItemCollection{testTombstone, &Tombstone{ID: "not-equal"}}, assertTombstoneWithTesting},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var logFn canErrorFunc
if tt.wantErr {
logFn = t.Logf
} else {
logFn = t.Errorf
}
if err := OnTombstone(tt.args.it, tt.args.fn(logFn, &testTombstone)); (err != nil) != tt.wantErr {
t.Errorf("OnTombstone() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}