mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-05-30 03:15:12 -04:00
* in the JMAP API as well as in several places in the Groupware framework, use a single jmap.Response[T] object to return the payload, the language, the session state and the etag/state instead of individual multi-valued return values
667 lines
20 KiB
Go
667 lines
20 KiB
Go
package jmap
|
|
|
|
import (
|
|
golog "log"
|
|
"maps"
|
|
"math/rand"
|
|
"regexp"
|
|
"slices"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"bytes"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/ProtonMail/go-crypto/openpgp"
|
|
"github.com/brianvoe/gofakeit/v7"
|
|
"github.com/opencloud-eu/opencloud/pkg/jscontact"
|
|
"github.com/opencloud-eu/opencloud/pkg/structs"
|
|
)
|
|
|
|
const (
|
|
// currently not supported, reported as https://github.com/stalwartlabs/stalwart/issues/2431
|
|
EnableMediaWithBlobId = false
|
|
)
|
|
|
|
type AddressBookBoxes struct {
|
|
sharedReadOnly bool
|
|
sharedReadWrite bool
|
|
sharedDelete bool
|
|
sortOrdered bool
|
|
}
|
|
|
|
func TestAddressBooks(t *testing.T) {
|
|
if skip(t) {
|
|
return
|
|
}
|
|
|
|
containerTest(t,
|
|
func(session *Session) string { return session.PrimaryAccounts.Contacts },
|
|
list,
|
|
getid,
|
|
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[AddressBookGetResponse], Error) {
|
|
return s.client.GetAddressbooks(accountId, ids, ctx)
|
|
},
|
|
func(s *StalwartTest, accountId string, id string, change AddressBookChange, ctx Context) (Result[AddressBook], Error) { //NOSONAR
|
|
return s.client.UpdateAddressBook(accountId, id, change, ctx)
|
|
},
|
|
func(s *StalwartTest, accountId string, ids []string, ctx Context) (Result[map[string]SetError], Error) { //NOSONAR
|
|
return s.client.DeleteAddressBook(accountId, ids, ctx)
|
|
},
|
|
func(s *StalwartTest, t *testing.T, accountId string, count uint, ctx Context, user User, principalIds []string) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
|
|
return s.fillAddressBook(t, accountId, count, ctx, user, principalIds)
|
|
},
|
|
func(orig AddressBook) AddressBookChange {
|
|
return AddressBookChange{
|
|
Description: ptr(orig.Description + " (changed)"),
|
|
IsSubscribed: ptr(!orig.IsSubscribed),
|
|
}
|
|
},
|
|
func(t *testing.T, orig AddressBook, _ AddressBookChange, changed AddressBook) {
|
|
require.Equal(t, orig.Name, changed.Name)
|
|
require.Equal(t, orig.Description+" (changed)", changed.Description)
|
|
require.Equal(t, !orig.IsSubscribed, changed.IsSubscribed)
|
|
},
|
|
)
|
|
}
|
|
|
|
func TestContacts(t *testing.T) {
|
|
if skip(t) {
|
|
return
|
|
}
|
|
|
|
count := uint(20 + rand.Intn(30))
|
|
|
|
require := require.New(t)
|
|
|
|
s, err := newStalwartTest(t)
|
|
require.NoError(err)
|
|
defer s.Close()
|
|
|
|
user := pickUser()
|
|
session := s.Session(user.name)
|
|
ctx := s.Context(session)
|
|
|
|
accountId, addressbookId, expectedContactCardsById, boxes, err := s.fillContacts(t, count, session, ctx, user)
|
|
require.NoError(err)
|
|
require.NotEmpty(accountId)
|
|
require.NotEmpty(addressbookId)
|
|
|
|
filter := ContactCardFilterCondition{
|
|
InAddressBook: addressbookId,
|
|
}
|
|
sortBy := []ContactCardComparator{
|
|
{Property: ContactCardPropertyCreated, IsAscending: true},
|
|
}
|
|
|
|
var results *ContactCardSearchResults
|
|
ss := EmptySessionState
|
|
os := EmptyState
|
|
{
|
|
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
|
|
require.NoError(err)
|
|
|
|
require.Len(result.Payload, 1)
|
|
require.Contains(result.Payload, accountId)
|
|
results = result.Payload[accountId]
|
|
require.Len(results.Results, int(count))
|
|
require.Nil(results.Limit)
|
|
require.NotNil(results.Position)
|
|
require.Equal(uint(0), *results.Position)
|
|
require.NotNil(results.Total)
|
|
require.Equal(count, *results.Total)
|
|
require.Equal(ChangeCalculation(true), results.CanCalculateChanges)
|
|
|
|
ss = result.GetSessionState()
|
|
require.NotEmpty(ss)
|
|
os = result.GetState()
|
|
require.NotEmpty(os)
|
|
}
|
|
|
|
for _, actual := range results.Results {
|
|
expected, ok := expectedContactCardsById[actual.Id]
|
|
require.True(ok, "failed to find created contact by its id")
|
|
matchContact(t, actual, expected)
|
|
}
|
|
|
|
// retrieve all objects at once
|
|
{
|
|
ids := structs.Map(results.Results, func(c ContactCard) string { return c.Id })
|
|
result, err := s.client.GetContactCards(accountId, ids, ctx)
|
|
require.NoError(err)
|
|
require.Empty(result.Payload.NotFound)
|
|
require.Len(result.Payload.List, len(ids))
|
|
byId := structs.Index(result.Payload.List, func(r ContactCard) string { return r.Id })
|
|
for _, actual := range results.Results {
|
|
expected, ok := byId[actual.Id]
|
|
require.True(ok, "failed to find created contact by its id")
|
|
matchContact(t, actual, expected)
|
|
}
|
|
}
|
|
|
|
// retrieve each object one by one
|
|
for _, actual := range results.Results {
|
|
result, err := s.client.GetContactCards(accountId, []string{actual.Id}, ctx)
|
|
require.NoError(err)
|
|
require.Len(result.Payload.List, 1)
|
|
matchContact(t, result.Payload.List[0], actual)
|
|
}
|
|
|
|
{
|
|
limit := uint(10)
|
|
slices := count / limit
|
|
remainder := count
|
|
require.Greater(slices, uint(1), "we need to have more than 10 objects in order to test the pagination of search results")
|
|
for i := range slices {
|
|
position := int(i * limit)
|
|
page := min(remainder, limit)
|
|
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, position, "", nil, &limit, true, ctx)
|
|
require.NoError(err)
|
|
require.Len(result.Payload, 1)
|
|
require.Contains(result.Payload, accountId)
|
|
results := result.Payload[accountId]
|
|
require.Equal(len(results.Results), int(page))
|
|
require.NotNil(results.Limit)
|
|
require.Equal(limit, *results.Limit)
|
|
require.NotNil(results.Position)
|
|
require.Equal(uint(position), *results.Position)
|
|
require.Equal(ChangeCalculation(true), results.CanCalculateChanges)
|
|
require.NotNil(results.Total)
|
|
require.Equal(count, *results.Total)
|
|
remainder -= uint(len(results.Results))
|
|
|
|
require.Equal(ss, result.GetSessionState())
|
|
}
|
|
}
|
|
|
|
{
|
|
chunkSize := 3
|
|
anchor := results.Results[0].Id
|
|
offset := 0
|
|
i := 0
|
|
for chunk := range slices.Chunk(results.Results, chunkSize) {
|
|
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, anchor, &offset, uintPtr(chunkSize), true, ctx)
|
|
require.Equal(ss, result.GetSessionState())
|
|
require.NoError(err)
|
|
require.Len(result.Payload, 1)
|
|
require.Contains(result.Payload, accountId)
|
|
results := result.Payload[accountId]
|
|
l := len(results.Results)
|
|
require.LessOrEqual(l, chunkSize)
|
|
require.NotZero(l)
|
|
require.NotNil(results.Limit)
|
|
require.Equal(uint(chunkSize), *results.Limit)
|
|
require.Equal(ChangeCalculation(true), results.CanCalculateChanges)
|
|
require.NotNil(results.Total)
|
|
require.Equal(count, *results.Total)
|
|
for i := range l {
|
|
require.Equal(chunk[i].Id, results.Results[i].Id)
|
|
}
|
|
anchor = chunk[len(chunk)-1].Id
|
|
offset = 1
|
|
i++
|
|
}
|
|
}
|
|
|
|
{
|
|
now := time.Now().Truncate(time.Duration(1) * time.Second).UTC()
|
|
for _, event := range expectedContactCardsById {
|
|
change := ContactCardChange{
|
|
Language: ptr("xyz"),
|
|
Updated: ptr(now),
|
|
}
|
|
result, err := s.client.UpdateContactCard(accountId, event.Id, change, ctx)
|
|
require.NoError(err)
|
|
require.Equal("xyz", result.Payload.Language)
|
|
require.Equal(now, result.Payload.Updated)
|
|
require.Equal(ss, result.GetSessionState())
|
|
require.NotEqual(os, result.GetState())
|
|
os = result.GetState()
|
|
}
|
|
}
|
|
{
|
|
ids := structs.Map(slices.Collect(maps.Values(expectedContactCardsById)), func(e ContactCard) string { return e.Id })
|
|
result, err := s.client.DeleteContactCard(accountId, ids, ctx)
|
|
require.NoError(err)
|
|
require.Empty(result.Payload)
|
|
|
|
require.Equal(ss, result.GetSessionState())
|
|
require.NotEqual(os, result.GetState())
|
|
os = result.GetState()
|
|
}
|
|
{
|
|
result, err := s.client.QueryContactCards([]string{accountId}, filter, sortBy, 0, "", nil, nil, true, ctx)
|
|
require.NoError(err)
|
|
require.Contains(result.Payload, accountId)
|
|
resp := result.Payload[accountId]
|
|
require.Empty(resp.Results)
|
|
require.NotNil(resp.Total)
|
|
require.Equal(uint(0), *resp.Total)
|
|
require.Equal(ss, result.GetSessionState())
|
|
require.Equal(os, result.GetState())
|
|
}
|
|
|
|
exceptions := []string{}
|
|
if !EnableMediaWithBlobId {
|
|
exceptions = append(exceptions, "mediaWithBlobId")
|
|
}
|
|
allBoxesAreTicked(t, boxes, exceptions...)
|
|
}
|
|
|
|
func matchContact(t *testing.T, actual ContactCard, expected ContactCard) {
|
|
// require.Equal(t, expected, actual)
|
|
deepEqual(t, expected, actual)
|
|
}
|
|
|
|
type ContactsBoxes struct {
|
|
nicknames bool
|
|
secondaryEmails bool
|
|
secondaryAddress bool
|
|
phones bool
|
|
onlineService bool
|
|
preferredLanguage bool
|
|
mediaWithBlobId bool
|
|
mediaWithDataUri bool
|
|
mediaWithExternalUri bool
|
|
organization bool
|
|
cryptoKey bool
|
|
link bool
|
|
}
|
|
|
|
var streetNumberRegex = regexp.MustCompile(`^(\d+)\s+(.+)$`)
|
|
|
|
func (s *StalwartTest) fillAddressBook( //NOSONAR
|
|
t *testing.T,
|
|
accountId string,
|
|
count uint,
|
|
ctx Context,
|
|
_ User,
|
|
principalIds []string,
|
|
) (AddressBookBoxes, []AddressBook, SessionState, State, error) {
|
|
require := require.New(t)
|
|
|
|
boxes := AddressBookBoxes{}
|
|
created := []AddressBook{}
|
|
ss := EmptySessionState
|
|
as := EmptyState
|
|
|
|
printer := func(s string) { golog.Println(s) }
|
|
|
|
for i := range count {
|
|
name := gofakeit.Company()
|
|
description := gofakeit.SentenceSimple()
|
|
subscribed := gofakeit.Bool()
|
|
abook := AddressBookChange{
|
|
Name: &name,
|
|
Description: &description,
|
|
IsSubscribed: &subscribed,
|
|
}
|
|
if i%2 == 0 {
|
|
abook.SortOrder = uintPtr(gofakeit.Uint())
|
|
boxes.sortOrdered = true
|
|
}
|
|
var sharing *AddressBookRights = nil
|
|
switch i % 4 {
|
|
default:
|
|
// no sharing
|
|
case 1:
|
|
sharing = &AddressBookRights{MayRead: true, MayWrite: true, MayAdmin: false, MayDelete: false}
|
|
boxes.sharedReadWrite = true
|
|
case 2:
|
|
sharing = &AddressBookRights{MayRead: true, MayWrite: false, MayAdmin: false, MayDelete: false}
|
|
boxes.sharedReadOnly = true
|
|
case 3:
|
|
sharing = &AddressBookRights{MayRead: true, MayWrite: true, MayAdmin: false, MayDelete: true}
|
|
boxes.sharedDelete = true
|
|
}
|
|
if sharing != nil {
|
|
numPrincipals := 1 + rand.Intn(len(principalIds)-1)
|
|
m := make(map[string]AddressBookRights, numPrincipals)
|
|
for _, p := range pickRandomN(numPrincipals, principalIds...) {
|
|
m[p] = *sharing
|
|
}
|
|
abook.ShareWith = m
|
|
}
|
|
|
|
result, err := s.client.CreateAddressBook(accountId, abook, ctx)
|
|
if err != nil {
|
|
return boxes, created, ss, as, err
|
|
}
|
|
require.NotEmpty(result.GetSessionState())
|
|
require.NotEmpty(result.GetState())
|
|
if ss != EmptySessionState {
|
|
require.Equal(ss, result.GetSessionState())
|
|
}
|
|
if as != EmptyState {
|
|
require.NotEqual(as, result.GetState())
|
|
}
|
|
require.NotNil(result.Payload)
|
|
created = append(created, *result.Payload)
|
|
ss = result.GetSessionState()
|
|
as = result.GetState()
|
|
|
|
printer(fmt.Sprintf("📔 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, result.Payload.Id))
|
|
}
|
|
return boxes, created, ss, as, nil
|
|
}
|
|
|
|
func (s *StalwartTest) fillContacts( //NOSONAR
|
|
t *testing.T,
|
|
count uint,
|
|
session *Session,
|
|
ctx Context,
|
|
user User,
|
|
) (string, string, map[string]ContactCard, ContactsBoxes, error) {
|
|
require := require.New(t)
|
|
c, err := NewTestJmapClient(session, user.name, user.password, true, true)
|
|
require.NoError(err)
|
|
defer c.Close()
|
|
|
|
boxes := ContactsBoxes{}
|
|
|
|
printer := func(s string) { golog.Println(s) }
|
|
|
|
accountId := c.session.PrimaryAccounts.Contacts
|
|
require.NotEmpty(accountId, "no primary account for contacts in session")
|
|
|
|
addressbookId := ""
|
|
{
|
|
addressBooksById, err := c.objectsById(accountId, AddressBookType)
|
|
require.NoError(err)
|
|
|
|
for id, addressbook := range addressBooksById {
|
|
if isDefault, ok := addressbook["isDefault"]; ok {
|
|
if isDefault.(bool) {
|
|
addressbookId = id
|
|
break
|
|
}
|
|
} else {
|
|
printer(fmt.Sprintf("abook without isDefault: %v", addressbook))
|
|
}
|
|
}
|
|
if addressbookId == "" {
|
|
ids := structs.Keys(addressBooksById)
|
|
slices.Sort(ids)
|
|
addressbookId = ids[0]
|
|
}
|
|
}
|
|
require.NotEmpty(addressbookId)
|
|
|
|
filled := map[string]ContactCard{}
|
|
for i := range count {
|
|
person := gofakeit.Person()
|
|
nameObj := createName(person)
|
|
language := pickLanguage()
|
|
|
|
card := ContactCardChange{
|
|
Type: jscontact.ContactCardType,
|
|
Version: ptr(jscontact.JSContactVersion_1_0),
|
|
AddressBookIds: toBoolPtrMap([]string{addressbookId}),
|
|
ProdId: &productName,
|
|
Language: &language,
|
|
Kind: ptr(jscontact.ContactCardKindIndividual),
|
|
Name: &nameObj,
|
|
}
|
|
|
|
if i%3 == 0 {
|
|
nicknameObj := createNickName(person)
|
|
id := id()
|
|
card.Nicknames = map[string]jscontact.Nickname{id: nicknameObj}
|
|
boxes.nicknames = true
|
|
}
|
|
|
|
{
|
|
emailObjs := map[string]jscontact.EmailAddress{}
|
|
emailId := id()
|
|
emailObj := createEmail(person, 10)
|
|
emailObjs[emailId] = emailObj
|
|
|
|
for i := range rand.Intn(3) {
|
|
id := id()
|
|
o := createSecondaryEmail(gofakeit.Email(), i*100)
|
|
emailObjs[id] = o
|
|
boxes.secondaryEmails = true
|
|
}
|
|
if len(emailObjs) > 0 {
|
|
card.Emails = emailObjs
|
|
}
|
|
}
|
|
if err := propmap(i%2 == 0, 1, 2, &card.Phones, func(i int, id string) (jscontact.Phone, error) {
|
|
boxes.phones = true
|
|
num := person.Contact.Phone
|
|
if i > 0 {
|
|
num = gofakeit.Phone()
|
|
}
|
|
var features map[jscontact.PhoneFeature]bool = nil
|
|
if rand.Intn(3) < 2 {
|
|
features = toBoolMapS(jscontact.PhoneFeatureMobile, jscontact.PhoneFeatureVoice, jscontact.PhoneFeatureVideo, jscontact.PhoneFeatureText)
|
|
} else {
|
|
features = toBoolMapS(jscontact.PhoneFeatureVoice, jscontact.PhoneFeatureMainNumber)
|
|
}
|
|
|
|
contexts := map[jscontact.PhoneContext]bool{jscontact.PhoneContextWork: true}
|
|
if rand.Intn(2) < 1 {
|
|
contexts[jscontact.PhoneContextPrivate] = true
|
|
}
|
|
tel := "tel:" + "+1" + num
|
|
return jscontact.Phone{
|
|
Type: jscontact.PhoneType,
|
|
Number: tel,
|
|
Features: features,
|
|
Contexts: contexts,
|
|
}, nil
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
if err := propmap(i%5 < 4, 1, 2, &card.Addresses, func(i int, id string) (jscontact.Address, error) {
|
|
var source *gofakeit.AddressInfo
|
|
if i == 0 {
|
|
source = person.Address
|
|
} else {
|
|
source = gofakeit.Address()
|
|
boxes.secondaryAddress = true
|
|
}
|
|
components := []jscontact.AddressComponent{}
|
|
m := streetNumberRegex.FindAllStringSubmatch(source.Street, -1)
|
|
if m != nil {
|
|
components = append(components, jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindName, Value: m[0][2]})
|
|
components = append(components, jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindNumber, Value: m[0][1]})
|
|
} else {
|
|
components = append(components, jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindName, Value: source.Street})
|
|
}
|
|
components = append(components,
|
|
jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindLocality, Value: source.City},
|
|
jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindCountry, Value: source.Country},
|
|
jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindRegion, Value: source.State},
|
|
jscontact.AddressComponent{Type: jscontact.AddressComponentType, Kind: jscontact.AddressComponentKindPostcode, Value: source.Zip},
|
|
)
|
|
tz := pickRandom(timezones...)
|
|
return jscontact.Address{
|
|
Type: jscontact.AddressType,
|
|
Components: components,
|
|
DefaultSeparator: ", ",
|
|
IsOrdered: true,
|
|
TimeZone: tz,
|
|
}, nil
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
if err := propmap(i%2 == 0, 1, 2, &card.OnlineServices, func(i int, id string) (jscontact.OnlineService, error) {
|
|
boxes.onlineService = true
|
|
switch rand.Intn(3) {
|
|
case 0:
|
|
return jscontact.OnlineService{
|
|
Type: jscontact.OnlineServiceType,
|
|
Service: "Mastodon",
|
|
User: "@" + person.Contact.Email,
|
|
Uri: "https://mastodon.example.com/@" + strings.ToLower(person.FirstName),
|
|
}, nil
|
|
case 1:
|
|
return jscontact.OnlineService{
|
|
Type: jscontact.OnlineServiceType,
|
|
Uri: "xmpp:" + person.Contact.Email,
|
|
}, nil
|
|
default:
|
|
return jscontact.OnlineService{
|
|
Type: jscontact.OnlineServiceType,
|
|
Service: "Discord",
|
|
User: person.Contact.Email,
|
|
Uri: "https://discord.example.com/user/" + person.Contact.Email,
|
|
}, nil
|
|
}
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
|
|
if err := propmap(i%3 == 0, 1, 2, &card.PreferredLanguages, func(i int, id string) (jscontact.LanguagePref, error) {
|
|
boxes.preferredLanguage = true
|
|
lang := pickRandom("en", "fr", "de", "es", "it")
|
|
contexts := pickRandoms1("work", "private")
|
|
return 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
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
|
|
if i%2 == 0 {
|
|
organizationObjs := map[string]jscontact.Organization{}
|
|
titleObjs := map[string]jscontact.Title{}
|
|
for range 1 + rand.Intn(2) {
|
|
boxes.organization = true
|
|
orgId := id()
|
|
titleId := id()
|
|
organizationObjs[orgId] = jscontact.Organization{
|
|
Type: jscontact.OrganizationType,
|
|
Name: person.Job.Company,
|
|
Contexts: toBoolMapS(jscontact.OrganizationContextWork),
|
|
}
|
|
|
|
titleObjs[titleId] = jscontact.Title{
|
|
Type: jscontact.TitleType,
|
|
Kind: jscontact.TitleKindTitle,
|
|
Name: person.Job.Title,
|
|
OrganizationId: orgId,
|
|
}
|
|
}
|
|
card.Organizations = organizationObjs
|
|
card.Titles = titleObjs
|
|
}
|
|
|
|
if err := propmap(i%2 == 0, 1, 1, &card.CryptoKeys, func(i int, id string) (jscontact.CryptoKey, error) {
|
|
boxes.cryptoKey = true
|
|
entity, err := openpgp.NewEntity(person.FirstName+" "+person.LastName, "test", person.Contact.Email, nil)
|
|
if err != nil {
|
|
return jscontact.CryptoKey{}, err
|
|
}
|
|
var b bytes.Buffer
|
|
err = entity.PrimaryKey.Serialize(&b)
|
|
if err != nil {
|
|
return jscontact.CryptoKey{}, err
|
|
}
|
|
encoded := base64.RawStdEncoding.EncodeToString(b.Bytes())
|
|
return jscontact.CryptoKey{
|
|
Type: jscontact.CryptoKeyType,
|
|
Uri: "data:application/pgp-keys;base64," + encoded,
|
|
MediaType: "application/pgp-keys",
|
|
}, nil
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
|
|
if err := propmap(i%2 == 0, 1, 2, &card.Media, func(i int, id string) (jscontact.Media, error) {
|
|
label := fmt.Sprintf("photo-%d", 1000+rand.Intn(9000))
|
|
|
|
r := 0
|
|
if EnableMediaWithBlobId {
|
|
r = rand.Intn(3)
|
|
} else {
|
|
r = rand.Intn(2)
|
|
}
|
|
|
|
switch r {
|
|
case 0:
|
|
boxes.mediaWithDataUri = true
|
|
// use data uri
|
|
//size := 16 + rand.Intn(512-16+1) // <- let's not do that right now, makes debugging errors very difficult due to the ASCII wall noise
|
|
size := pickRandom(16, 24, 32, 48, 64)
|
|
img := gofakeit.ImagePng(size, size)
|
|
mime := "image/png"
|
|
uri := "data:" + mime + ";base64," + base64.StdEncoding.EncodeToString(img)
|
|
contexts := toBoolMapS(jscontact.MediaContextPrivate)
|
|
return jscontact.Media{
|
|
Type: jscontact.MediaType,
|
|
Kind: jscontact.MediaKindPhoto,
|
|
Uri: uri,
|
|
MediaType: mime,
|
|
Contexts: contexts,
|
|
Label: label,
|
|
}, nil
|
|
|
|
case 1:
|
|
boxes.mediaWithExternalUri = true
|
|
// use external uri
|
|
uri := externalImageUri()
|
|
contexts := toBoolMapS(jscontact.MediaContextWork)
|
|
return jscontact.Media{
|
|
Type: jscontact.MediaType,
|
|
Kind: jscontact.MediaKindPhoto,
|
|
Uri: uri,
|
|
Contexts: contexts,
|
|
Label: label,
|
|
}, nil
|
|
|
|
default:
|
|
boxes.mediaWithBlobId = true
|
|
size := pickRandom(16, 24, 32, 48, 64)
|
|
img := gofakeit.ImageJpeg(size, size)
|
|
blob, err := c.uploadBlob(accountId, img, "image/jpeg")
|
|
if err != nil {
|
|
return jscontact.Media{}, err
|
|
}
|
|
contexts := toBoolMapS(jscontact.MediaContextPrivate)
|
|
return jscontact.Media{
|
|
Type: jscontact.MediaType,
|
|
Kind: jscontact.MediaKindPhoto,
|
|
BlobId: blob.BlobId,
|
|
MediaType: blob.Type,
|
|
Contexts: contexts,
|
|
Label: label,
|
|
}, nil
|
|
|
|
}
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
if err := propmap(i%2 == 0, 1, 1, &card.Links, func(i int, id string) (jscontact.Link, error) {
|
|
boxes.link = true
|
|
return jscontact.Link{
|
|
Type: jscontact.LinkType,
|
|
Kind: jscontact.LinkKindContact,
|
|
Uri: "mailto:" + person.Contact.Email,
|
|
Pref: uint((i + 1) * 10),
|
|
}, nil
|
|
}); err != nil {
|
|
return "", "", nil, boxes, err
|
|
}
|
|
|
|
result, err := s.client.CreateContactCard(accountId, card, ctx)
|
|
if err != nil {
|
|
return accountId, addressbookId, filled, boxes, err
|
|
}
|
|
require.NotNil(result.Payload)
|
|
filled[result.Payload.Id] = *result.Payload
|
|
printer(fmt.Sprintf("🧑🏻 created %*s/%v id=%v", int(math.Log10(float64(count))+1), strconv.Itoa(int(i+1)), count, result.Payload.Id))
|
|
}
|
|
return accountId, addressbookId, filled, boxes, nil
|
|
}
|