mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-15 00:31:30 -05:00
groupware: add getting a contact by ID + add integration tests for contacts
This commit is contained in:
@@ -37,6 +37,32 @@ func (j *Client) GetAddressbooks(accountId string, session *Session, ctx context
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) GetContactCardsById(accountId string, session *Session, ctx context.Context, logger *log.Logger,
|
||||
acceptLanguage string, contactIds []string) (map[string]jscontact.ContactCard, SessionState, State, Language, Error) {
|
||||
logger = j.logger("GetContactCardsById", session, logger)
|
||||
|
||||
cmd, err := j.request(session, logger, invocation(CommandContactCardGet, ContactCardGetCommand{
|
||||
Ids: contactIds,
|
||||
AccountId: accountId,
|
||||
}, "0"))
|
||||
if err != nil {
|
||||
return nil, "", "", "", err
|
||||
}
|
||||
|
||||
return command(j.api, logger, ctx, session, j.onSessionOutdated, cmd, acceptLanguage, func(body *Response) (map[string]jscontact.ContactCard, State, Error) {
|
||||
var response ContactCardGetResponse
|
||||
err = retrieveResponseMatchParameters(logger, body, CommandContactCardGet, "0", &response)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
m := map[string]jscontact.ContactCard{}
|
||||
for _, contact := range response.List {
|
||||
m[contact.Id] = contact
|
||||
}
|
||||
return m, response.State, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (j *Client) QueryContactCards(accountIds []string, session *Session, ctx context.Context, logger *log.Logger, acceptLanguage string,
|
||||
filter ContactCardFilterElement, sortBy []ContactCardComparator,
|
||||
position uint, limit uint) (map[string][]jscontact.ContactCard, SessionState, State, Language, Error) {
|
||||
|
||||
85
pkg/jmap/jmap_integration_contact_test.go
Normal file
85
pkg/jmap/jmap_integration_contact_test.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package jmap
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/jscontact"
|
||||
)
|
||||
|
||||
func TestContacts(t *testing.T) {
|
||||
if skip(t) {
|
||||
return
|
||||
}
|
||||
|
||||
count := uint(15 + rand.Intn(20))
|
||||
|
||||
require := require.New(t)
|
||||
|
||||
s, err := newStalwartTest(t)
|
||||
require.NoError(err)
|
||||
defer s.Close()
|
||||
|
||||
accountId, addressbookId, cardsById, err := s.fillContacts(t, count)
|
||||
require.NoError(err)
|
||||
require.NotEmpty(accountId)
|
||||
require.NotEmpty(addressbookId)
|
||||
|
||||
filter := ContactCardFilterCondition{
|
||||
InAddressBook: addressbookId,
|
||||
}
|
||||
sortBy := []ContactCardComparator{
|
||||
{Property: jscontact.ContactCardPropertyCreated, IsAscending: true},
|
||||
}
|
||||
|
||||
contactsByAccount, _, _, _, err := s.client.QueryContactCards([]string{accountId}, s.session, t.Context(), s.logger, "", filter, sortBy, 0, 0)
|
||||
require.NoError(err)
|
||||
|
||||
require.Len(contactsByAccount, 1)
|
||||
require.Contains(contactsByAccount, accountId)
|
||||
contacts := contactsByAccount[accountId]
|
||||
require.Len(contacts, int(count))
|
||||
|
||||
for _, actual := range contacts {
|
||||
expected, ok := cardsById[actual.Id]
|
||||
require.True(ok, "failed to find created contact by its id")
|
||||
expected.Id = actual.Id
|
||||
matchContact(t, actual, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func matchContact(t *testing.T, actual jscontact.ContactCard, expected jscontact.ContactCard) {
|
||||
require := require.New(t)
|
||||
|
||||
//a := actual
|
||||
//unType(t, &a)
|
||||
|
||||
require.Equal(expected, actual)
|
||||
}
|
||||
|
||||
func unType(t *testing.T, s any) {
|
||||
ty := reflect.TypeOf(s)
|
||||
|
||||
switch ty.Kind() {
|
||||
case reflect.Map, reflect.Array, reflect.Pointer, reflect.Slice:
|
||||
ty = ty.Elem()
|
||||
}
|
||||
|
||||
switch ty.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := range ty.NumField() {
|
||||
f := ty.Field(i)
|
||||
n := f.Name
|
||||
if n == "Type" {
|
||||
v := reflect.ValueOf(s).Field(i)
|
||||
require.True(t, v.Elem().CanSet(), "cannot set the Type field")
|
||||
v.SetString("")
|
||||
} else {
|
||||
//unType(t, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func TestEmails(t *testing.T) {
|
||||
var threads int = 0
|
||||
var mails []filledMail = nil
|
||||
{
|
||||
mails, threads, err = s.fill(inboxFolder, count)
|
||||
mails, threads, err = s.fillEmailsWithImap(inboxFolder, count)
|
||||
require.NoError(err)
|
||||
}
|
||||
mailsByMessageId := structs.Index(mails, func(mail filledMail) string { return mail.messageId })
|
||||
@@ -119,11 +119,11 @@ func TestEmails(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func matchEmail(t *testing.T, actual Email, e filledMail, hasBodies bool) {
|
||||
func matchEmail(t *testing.T, actual Email, expected filledMail, hasBodies bool) {
|
||||
require := require.New(t)
|
||||
require.Len(actual.MessageId, 1)
|
||||
require.Equal(e.messageId, actual.MessageId[0])
|
||||
require.Equal(e.subject, actual.Subject)
|
||||
require.Equal(expected.messageId, actual.MessageId[0])
|
||||
require.Equal(expected.subject, actual.Subject)
|
||||
require.NotEmpty(actual.Preview)
|
||||
if hasBodies {
|
||||
require.Len(actual.TextBody, 1)
|
||||
@@ -135,7 +135,7 @@ func matchEmail(t *testing.T, actual Email, e filledMail, hasBodies bool) {
|
||||
} else {
|
||||
require.Empty(actual.BodyValues)
|
||||
}
|
||||
require.ElementsMatch(slices.Collect(maps.Keys(actual.Keywords)), e.keywords)
|
||||
require.ElementsMatch(slices.Collect(maps.Keys(actual.Keywords)), expected.keywords)
|
||||
|
||||
{
|
||||
list := make([]filledAttachment, len(actual.Attachments))
|
||||
@@ -150,6 +150,6 @@ func matchEmail(t *testing.T, actual Email, e filledMail, hasBodies bool) {
|
||||
require.NotEmpty(a.PartId)
|
||||
}
|
||||
|
||||
require.ElementsMatch(list, e.attachments)
|
||||
require.ElementsMatch(list, expected.attachments)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -918,8 +918,10 @@ type SessionPrimaryAccounts struct {
|
||||
Quota string `json:"urn:ietf:params:jmap:quota,omitempty"`
|
||||
Websocket string `json:"urn:ietf:params:jmap:websocket,omitempty"`
|
||||
Task string `json:"urn:ietf:params:jmap:task,omitempty"`
|
||||
Calendar string `json:"urn:ietf:params:jmap:calendar,omitempty"`
|
||||
Contact string `json:"urn:ietf:params:jmap:contact,omitempty"`
|
||||
Calendars string `json:"urn:ietf:params:jmap:calendars,omitempty"`
|
||||
CalendarsParse string `json:"urn:ietf:params:jmap:calendars:parse,omitempty"`
|
||||
Contacts string `json:"urn:ietf:params:jmap:contacts,omitempty"`
|
||||
ContactsParse string `json:"urn:ietf:params:jmap:contacts:parse,omitempty"`
|
||||
}
|
||||
|
||||
type SessionState string
|
||||
|
||||
@@ -587,7 +587,7 @@ const (
|
||||
|
||||
// PhoneFeatures.
|
||||
|
||||
PhoneFeatureMobile = PhoneFeature("mobile")
|
||||
PhoneFeatureMobile = PhoneFeature("cell") // TODO the spec says 'mobile', but Stalwart only supports 'cell'
|
||||
PhoneFeatureVoice = PhoneFeature("voice")
|
||||
PhoneFeatureText = PhoneFeature("text")
|
||||
PhoneFeatureVideo = PhoneFeature("video")
|
||||
|
||||
@@ -55,18 +55,50 @@ func Index[K comparable, V any](source []V, indexer func(V) K) map[K]V {
|
||||
return result
|
||||
}
|
||||
|
||||
func Map[E any, R any](source []E, indexer func(E) R) []R {
|
||||
func Map[E any, R any](source []E, mapper func(E) R) []R {
|
||||
if source == nil {
|
||||
var zero []R
|
||||
return zero
|
||||
}
|
||||
result := make([]R, len(source))
|
||||
for i, e := range source {
|
||||
result[i] = indexer(e)
|
||||
result[i] = mapper(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func MapValues[K comparable, S any, T any](m map[K]S, mapper func(S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapValues2[K comparable, S any, T any](m map[K]S, mapper func(K, S) T) map[K]T {
|
||||
r := make(map[K]T, len(m))
|
||||
for k, s := range m {
|
||||
r[k] = mapper(k, s)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapKeys[S comparable, T comparable, V any](m map[S]V, mapper func(S) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func MapKeys2[S comparable, T comparable, V any](m map[S]V, mapper func(S, V) T) map[T]V {
|
||||
r := make(map[T]V, len(m))
|
||||
for s, v := range m {
|
||||
r[mapper(s, v)] = v
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func ToBoolMap[E comparable](source []E) map[E]bool {
|
||||
m := make(map[E]bool, len(source))
|
||||
for _, v := range source {
|
||||
|
||||
@@ -148,6 +148,32 @@ func (g *Groupware) GetContactsInAddressbook(w http.ResponseWriter, r *http.Requ
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) GetContactById(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
if !ok {
|
||||
return resp
|
||||
}
|
||||
|
||||
l := req.logger.With()
|
||||
|
||||
contactId := chi.URLParam(r, UriParamContactId)
|
||||
l = l.Str(UriParamContactId, log.SafeString(contactId))
|
||||
|
||||
logger := log.From(l)
|
||||
contactsById, sessionState, state, lang, jerr := g.jmap.GetContactCardsById(accountId, req.session, req.ctx, logger, req.language(), []string{contactId})
|
||||
if jerr != nil {
|
||||
return req.errorResponseFromJmap(jerr)
|
||||
}
|
||||
|
||||
if contact, ok := contactsById[contactId]; ok {
|
||||
return etagResponse(contact, sessionState, state, lang)
|
||||
} else {
|
||||
return notFoundResponse(sessionState)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (g *Groupware) CreateContact(w http.ResponseWriter, r *http.Request) {
|
||||
g.respond(w, r, func(req Request) Response {
|
||||
ok, accountId, resp := req.needContactWithAccount()
|
||||
|
||||
@@ -148,6 +148,7 @@ func (g *Groupware) Route(r chi.Router) {
|
||||
r.Route("/contacts", func(r chi.Router) {
|
||||
r.Post("/", g.CreateContact)
|
||||
r.Delete("/{contactid}", g.DeleteContact)
|
||||
r.Get("/{contactid}", g.GetContactById)
|
||||
})
|
||||
r.Route("/calendars", func(r chi.Router) {
|
||||
r.Get("/", g.GetCalendars)
|
||||
|
||||
Reference in New Issue
Block a user