groupware: improve JMAP ContactCard integration tests

This commit is contained in:
Pascal Bleser
2025-11-19 10:37:10 +01:00
parent e27df2cdc9
commit 5dc9f71040
3 changed files with 72 additions and 91 deletions

View File

@@ -1,7 +1,6 @@
package jmap
import (
"fmt"
"math/rand"
"reflect"
"slices"
@@ -10,7 +9,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/opencloud-eu/opencloud/pkg/jscontact"
"github.com/opencloud-eu/opencloud/pkg/structs"
)
func TestContacts(t *testing.T) {
@@ -26,7 +24,7 @@ func TestContacts(t *testing.T) {
require.NoError(err)
defer s.Close()
accountId, addressbookId, cardsById, sentById, boxes, err := s.fillContacts(t, count)
accountId, addressbookId, expectedContactCardsById, boxes, err := s.fillContacts(t, count)
require.NoError(err)
require.NotEmpty(accountId)
require.NotEmpty(addressbookId)
@@ -49,17 +47,9 @@ func TestContacts(t *testing.T) {
require.Len(contacts, int(count))
for _, actual := range contacts {
expected, ok := cardsById[actual.Id]
expected, ok := expectedContactCardsById[actual.Id]
require.True(ok, "failed to find created contact by its id")
sent := sentById[actual.Id]
matchContact(t, actual, expected, sent, func() (jscontact.ContactCard, error) {
cards, _, _, _, err := s.client.GetContactCardsById(accountId, s.session, t.Context(), s.logger, "", []string{actual.Id})
if err != nil {
return jscontact.ContactCard{}, err
}
require.Contains(cards, actual.Id)
return cards[actual.Id], nil
})
matchContact(t, actual, expected)
}
}
@@ -76,23 +66,6 @@ func allTrue[S any](t *testing.T, s S, exceptions ...string) {
}
}
func matchContact(t *testing.T, actual jscontact.ContactCard, expected jscontact.ContactCard, sent map[string]any, fetcher func() (jscontact.ContactCard, error)) {
require := require.New(t)
if structs.AnyValue(expected.Media, func(media jscontact.Media) bool { return media.BlobId != "" }) {
fmt.Printf("\x1b[33;1m----------------------------------------------------------\x1b[0m\n")
fmt.Printf("\x1b[45;1m expected media: \x1b[0m\n%v\n\n", expected.Media)
fmt.Printf("\x1b[46;1m actual media: \x1b[0m\n%v\n\n", actual.Media)
fmt.Printf("\x1b[43;1m sent: \x1b[0m\n%v\n\n", sent)
fmt.Printf("\x1b[44;1m pulling: \x1b[0m\n")
_, err := fetcher()
require.NoError(err)
fmt.Printf("\x1b[44;1m pulled. \x1b[0m\n")
}
require.Equal(expected.Name, actual.Name)
require.Equal(expected.Emails, actual.Emails)
require.Equal(expected.Organizations, actual.Organizations)
require.Equal(expected.Media, actual.Media)
require.Equal(expected, actual)
func matchContact(t *testing.T, actual jscontact.ContactCard, expected jscontact.ContactCard) {
require.Equal(t, expected, actual)
}

View File

@@ -18,6 +18,7 @@ import (
"net/mail"
"net/url"
"os"
"reflect"
"regexp"
"slices"
"strconv"
@@ -792,7 +793,7 @@ type ContactsBoxes struct {
func (s *StalwartTest) fillContacts(
t *testing.T,
count uint,
) (string, string, map[string]jscontact.ContactCard, map[string]map[string]any, ContactsBoxes, error) {
) (string, string, map[string]jscontact.ContactCard, ContactsBoxes, error) {
require := require.New(t)
c, err := NewTestJmapClient(s.session, s.username, s.password, true, true)
require.NoError(err)
@@ -821,8 +822,9 @@ func (s *StalwartTest) fillContacts(
}
require.NotEmpty(addressbookId)
u := true
filled := map[string]jscontact.ContactCard{}
sent := map[string]map[string]any{}
for i := range count {
person := gofakeit.Person()
nameMap, nameObj := createName(person)
@@ -896,14 +898,14 @@ func (s *StalwartTest) fillContacts(
"number": tel,
"features": structs.MapKeys(features, func(f jscontact.PhoneFeature) string { return string(f) }),
"contexts": structs.MapKeys(contexts, func(c jscontact.PhoneContext) string { return string(c) }),
}, jscontact.Phone{
//Type: jscontact.PhoneType,
}, untype(jscontact.Phone{
Type: jscontact.PhoneType,
Number: tel,
Features: features,
Contexts: contexts,
}, nil
}, u), nil
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if err := propmap(i%5 < 4, 1, 2, contact, "addresses", &card.Addresses, func(i int, id string) (map[string]any, jscontact.Address, error) {
var source *gofakeit.AddressInfo
@@ -936,15 +938,15 @@ func (s *StalwartTest) fillContacts(
"defaultSeparator": ", ",
"isOrdered": true,
"timeZone": tz,
}, jscontact.Address{
//Type: jscontact.AddressType,
}, untype(jscontact.Address{
Type: jscontact.AddressType,
Components: components,
DefaultSeparator: ", ",
IsOrdered: true,
TimeZone: tz,
}, nil
}, u), nil
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if err := propmap(i%2 == 0, 1, 2, contact, "onlineServices", &card.OnlineServices, func(i int, id string) (map[string]any, jscontact.OnlineService, error) {
boxes.onlineService = true
@@ -955,35 +957,35 @@ func (s *StalwartTest) fillContacts(
"service": "Mastodon",
"user": "@" + person.Contact.Email,
"uri": "https://mastodon.example.com/@" + strings.ToLower(person.FirstName),
}, jscontact.OnlineService{
//Type: jscontact.OnlineServiceType,
}, untype(jscontact.OnlineService{
Type: jscontact.OnlineServiceType,
Service: "Mastodon",
User: "@" + person.Contact.Email,
Uri: "https://mastodon.example.com/@" + strings.ToLower(person.FirstName),
}, nil
}, u), nil
case 1:
return map[string]any{
"@type": "OnlineService",
"uri": "xmpp:" + person.Contact.Email,
}, jscontact.OnlineService{
//Type: jscontact.OnlineServiceType,
Uri: "xmpp:" + person.Contact.Email,
}, nil
}, untype(jscontact.OnlineService{
Type: jscontact.OnlineServiceType,
Uri: "xmpp:" + person.Contact.Email,
}, u), nil
default:
return map[string]any{
"@type": "OnlineService",
"service": "Discord",
"user": person.Contact.Email,
"uri": "https://discord.example.com/user/" + person.Contact.Email,
}, jscontact.OnlineService{
//Type: jscontact.OnlineServiceType,
}, untype(jscontact.OnlineService{
Type: jscontact.OnlineServiceType,
Service: "Discord",
User: person.Contact.Email,
Uri: "https://discord.example.com/user/" + person.Contact.Email,
}, nil
}, u), nil
}
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if err := propmap(i%3 == 0, 1, 2, contact, "preferredLanguages", &card.PreferredLanguages, func(i int, id string) (map[string]any, jscontact.LanguagePref, error) {
@@ -995,14 +997,14 @@ func (s *StalwartTest) fillContacts(
"language": lang,
"contexts": toBoolMap(contexts),
"pref": i + 1,
}, jscontact.LanguagePref{
// Type: jscontact.LanguagePrefType,
}, untype(jscontact.LanguagePref{
Type: jscontact.LanguagePrefType,
Language: lang,
Contexts: toBoolMap(structs.Map(contexts, func(s string) jscontact.LanguagePrefContext { return jscontact.LanguagePrefContext(s) })),
Pref: uint(i + 1),
}, nil
}, u), nil
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if i%2 == 0 {
@@ -1019,23 +1021,23 @@ func (s *StalwartTest) fillContacts(
"name": person.Job.Company,
"contexts": toBoolMapS("work"),
}
organizationObjs[orgId] = jscontact.Organization{
// Type: jscontact.OrganizationType,
organizationObjs[orgId] = untype(jscontact.Organization{
Type: jscontact.OrganizationType,
Name: person.Job.Company,
Contexts: toBoolMapS(jscontact.OrganizationContextWork),
}
}, u)
titleMaps[titleId] = map[string]any{
"@type": "Title",
"kind": "title",
"name": person.Job.Title,
"organizationId": orgId,
}
titleObjs[titleId] = jscontact.Title{
// Type: jscontact.TitleType,
titleObjs[titleId] = untype(jscontact.Title{
Type: jscontact.TitleType,
Kind: jscontact.TitleKindTitle,
Name: person.Job.Title,
OrganizationId: orgId,
}
}, u)
}
contact["organizations"] = organizationMaps
contact["titles"] = titleMaps
@@ -1058,12 +1060,12 @@ func (s *StalwartTest) fillContacts(
return map[string]any{
"@type": "CryptoKey",
"uri": "data:application/pgp-keys;base64," + encoded,
}, jscontact.CryptoKey{
// Type: jscontact.CryptoKeyType,
Uri: "data:application/pgp-keys;base64," + encoded,
}, nil
}, untype(jscontact.CryptoKey{
Type: jscontact.CryptoKeyType,
Uri: "data:application/pgp-keys;base64," + encoded,
}, u), nil
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if err := propmap(i%2 == 0, 1, 2, contact, "media", &card.Media, func(i int, id string) (map[string]any, jscontact.Media, error) {
@@ -1085,16 +1087,16 @@ func (s *StalwartTest) fillContacts(
"mediaType": mime,
"contexts": structs.MapKeys(contexts, func(c jscontact.MediaContext) string { return string(c) }),
"label": label,
}, jscontact.Media{
// Type: jscontact.MediaType,
}, untype(jscontact.Media{
Type: jscontact.MediaType,
Kind: jscontact.MediaKindPhoto,
Uri: uri,
MediaType: mime,
Contexts: contexts,
Label: label,
}, nil
// currently does not work, reported as https://github.com/stalwartlabs/stalwart/issues/2431
case 99: // change this to 1 to enable it again
}, u), nil
// currently not supported, reported as https://github.com/stalwartlabs/stalwart/issues/2431
case -1: // change this to 1 to enable it again
boxes.mediaWithBlobId = true
size := pickRandom(16, 24, 32, 48, 64)
img := gofakeit.ImageJpeg(size, size)
@@ -1109,14 +1111,14 @@ func (s *StalwartTest) fillContacts(
"blobId": blob.BlobId,
"contexts": structs.MapKeys(contexts, func(c jscontact.MediaContext) string { return string(c) }),
"label": label,
}, jscontact.Media{
// Type: jscontact.MediaType,
}, untype(jscontact.Media{
Type: jscontact.MediaType,
Kind: jscontact.MediaKindPhoto,
BlobId: blob.BlobId,
MediaType: blob.Type,
Contexts: contexts,
Label: label,
}, nil
}, u), nil
default:
boxes.mediaWithExternalUri = true
@@ -1129,16 +1131,16 @@ func (s *StalwartTest) fillContacts(
"uri": uri,
"contexts": structs.MapKeys(contexts, func(c jscontact.MediaContext) string { return string(c) }),
"label": label,
}, jscontact.Media{
// Type: jscontact.MediaType,
}, untype(jscontact.Media{
Type: jscontact.MediaType,
Kind: jscontact.MediaKindPhoto,
Uri: uri,
Contexts: contexts,
Label: label,
}, nil
}, u), nil
}
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
if err := propmap(i%2 == 0, 1, 1, contact, "links", &card.Links, func(i int, id string) (map[string]any, jscontact.Link, error) {
boxes.link = true
@@ -1147,26 +1149,32 @@ func (s *StalwartTest) fillContacts(
"kind": "contact",
"uri": "mailto:" + person.Contact.Email,
"pref": (i + 1) * 10,
}, jscontact.Link{
// Type: jscontact.LinkType,
}, untype(jscontact.Link{
Type: jscontact.LinkType,
Kind: jscontact.LinkKindContact,
Uri: "mailto:" + person.Contact.Email,
Pref: uint((i + 1) * 10),
}, nil
}, u), nil
}); err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
id, err := s.CreateContact(c, accountId, contact)
if err != nil {
return "", "", nil, nil, boxes, err
return "", "", nil, boxes, err
}
card.Id = id
filled[id] = card
sent[id] = contact
printer(fmt.Sprintf("🧑🏻 created %*s/%v uid=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, id))
}
return accountId, addressbookId, filled, sent, boxes, nil
return accountId, addressbookId, filled, boxes, nil
}
func untype[S any](s S, t bool) S {
if t {
reflect.ValueOf(&s).Elem().FieldByName("Type").SetString("")
}
return s
}
func (s *StalwartTest) CreateContact(j *TestJmapClient, accountId string, contact map[string]any) (string, error) {

View File

@@ -149,9 +149,9 @@ func TestAnyValue(t *testing.T) {
assert.True(t, AnyValue(map[string]bool{"a": true, "b": false}, always))
assert.False(t, AnyValue(map[string]bool{}, always))
assert.False(t, AnyValue[string, bool](nil, always))
assert.False(t, AnyValue[string](nil, always))
assert.False(t, AnyValue(map[string]bool{"a": true, "b": false}, never))
assert.False(t, AnyValue[string, bool](nil, never))
assert.False(t, AnyValue[string](nil, never))
}
func TestAnyItem(t *testing.T) {
@@ -160,7 +160,7 @@ func TestAnyItem(t *testing.T) {
assert.True(t, AnyItem(map[string]bool{"a": true, "b": false}, always))
assert.False(t, AnyItem(map[string]bool{}, always))
assert.False(t, AnyItem[string, bool](nil, always))
assert.False(t, AnyItem(nil, always))
assert.False(t, AnyItem(map[string]bool{"a": true, "b": false}, never))
assert.False(t, AnyItem[string, bool](nil, never))
assert.False(t, AnyItem(nil, never))
}