From 70328a50f795b780dfe45f869645bcb2a1efce3f Mon Sep 17 00:00:00 2001
From: Pascal Bleser
Date: Wed, 19 Nov 2025 10:37:10 +0100
Subject: [PATCH] groupware: improve JMAP ContactCard integration tests
---
pkg/jmap/jmap_integration_contact_test.go | 37 +------
pkg/jmap/jmap_integration_test.go | 118 ++++++++++++----------
pkg/structs/structs_test.go | 8 +-
3 files changed, 72 insertions(+), 91 deletions(-)
diff --git a/pkg/jmap/jmap_integration_contact_test.go b/pkg/jmap/jmap_integration_contact_test.go
index 6ed729b793..2daa684ecb 100644
--- a/pkg/jmap/jmap_integration_contact_test.go
+++ b/pkg/jmap/jmap_integration_contact_test.go
@@ -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)
}
diff --git a/pkg/jmap/jmap_integration_test.go b/pkg/jmap/jmap_integration_test.go
index 922674d23e..85ac294792 100644
--- a/pkg/jmap/jmap_integration_test.go
+++ b/pkg/jmap/jmap_integration_test.go
@@ -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) {
diff --git a/pkg/structs/structs_test.go b/pkg/structs/structs_test.go
index b6a8ac1213..f56485c7c4 100644
--- a/pkg/structs/structs_test.go
+++ b/pkg/structs/structs_test.go
@@ -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))
}