mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-02-11 22:49:21 -06:00
* add URL to retrieve all the mailboxes for all the accounts of a user, as a first use-case for an all-accounts operation, as /accounts/all/mailboxes * add URL to retrieve mailbox changes for all the mailboxes of all the accounts of a user, as a first use-case for an all-accounts operation, as /accounts/all/mailboxes/changes * change the defaultAccountId from '*' to '_', as '*' rather indicates "all" than "default", and we might want to use that for "all accounts" operations in the future * refactor(groupware): remove the accountId parameter from the logger() function, as it is not used anyways, but also confusing for operations that support multiple account ids
243 lines
6.3 KiB
Go
243 lines
6.3 KiB
Go
package jmap
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha512"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"math/rand"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/opencloud-eu/opencloud/pkg/log"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type TestJmapWellKnownClient struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func NewTestJmapWellKnownClient(t *testing.T) SessionClient {
|
|
return &TestJmapWellKnownClient{t: t}
|
|
}
|
|
|
|
func (t *TestJmapWellKnownClient) Close() error {
|
|
return nil
|
|
}
|
|
|
|
func (t *TestJmapWellKnownClient) GetSession(sessionUrl *url.URL, username string, logger *log.Logger) (SessionResponse, Error) {
|
|
pa := generateRandomString(2 + seededRand.Intn(10))
|
|
return SessionResponse{
|
|
Username: generateRandomString(8),
|
|
ApiUrl: "test://",
|
|
PrimaryAccounts: SessionPrimaryAccounts{
|
|
Core: pa,
|
|
Mail: pa,
|
|
Submission: pa,
|
|
VacationResponse: pa,
|
|
Sieve: pa,
|
|
Blob: pa,
|
|
Quota: pa,
|
|
Websocket: pa,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
type TestJmapApiClient struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func NewTestJmapApiClient(t *testing.T) ApiClient {
|
|
return &TestJmapApiClient{t: t}
|
|
}
|
|
|
|
func (t TestJmapApiClient) Close() error {
|
|
return nil
|
|
}
|
|
|
|
type TestJmapBlobClient struct {
|
|
t *testing.T
|
|
}
|
|
|
|
func NewTestJmapBlobClient(t *testing.T) BlobClient {
|
|
return &TestJmapBlobClient{t: t}
|
|
}
|
|
|
|
func (t TestJmapBlobClient) UploadBinary(ctx context.Context, logger *log.Logger, session *Session, uploadUrl string, endpoint string, contentType string, body io.Reader) (UploadedBlob, Error) {
|
|
bytes, err := io.ReadAll(body)
|
|
if err != nil {
|
|
return UploadedBlob{}, SimpleError{code: 0, err: err}
|
|
}
|
|
hasher := sha512.New()
|
|
hasher.Write(bytes)
|
|
return UploadedBlob{
|
|
Id: uuid.NewString(),
|
|
Size: len(bytes),
|
|
Type: contentType,
|
|
Sha512: base64.StdEncoding.EncodeToString(hasher.Sum(nil)),
|
|
}, nil
|
|
}
|
|
|
|
func (h *TestJmapBlobClient) DownloadBinary(ctx context.Context, logger *log.Logger, session *Session, downloadUrl string, endpoint string) (*BlobDownload, Error) {
|
|
return &BlobDownload{
|
|
Body: io.NopCloser(strings.NewReader("")),
|
|
Size: -1,
|
|
Type: "text/plain",
|
|
ContentDisposition: "attachment; filename=\"file.txt\"",
|
|
CacheControl: "",
|
|
}, nil
|
|
}
|
|
|
|
func serveTestFile(t *testing.T, name string) ([]byte, Error) {
|
|
cwd, _ := os.Getwd()
|
|
p := filepath.Join(cwd, "testdata", name)
|
|
bytes, err := os.ReadFile(p)
|
|
if err != nil {
|
|
return bytes, SimpleError{code: 0, err: err}
|
|
}
|
|
// try to parse it first to avoid any deeper issues that are caused by the test tools
|
|
var target map[string]any
|
|
err = json.Unmarshal(bytes, &target)
|
|
if err != nil {
|
|
t.Errorf("failed to parse JSON test data file '%v': %v", p, err)
|
|
return nil, SimpleError{code: 0, err: err}
|
|
}
|
|
return bytes, nil
|
|
}
|
|
|
|
func (t *TestJmapApiClient) Command(ctx context.Context, logger *log.Logger, session *Session, request Request) ([]byte, Error) {
|
|
command := request.MethodCalls[0].Command
|
|
switch command {
|
|
case CommandMailboxGet:
|
|
return serveTestFile(t.t, "mailboxes1.json")
|
|
case CommandEmailQuery:
|
|
return serveTestFile(t.t, "mails1.json")
|
|
default:
|
|
require.Fail(t.t, "TestJmapApiClient: unsupported jmap command: %v", command)
|
|
return nil, SimpleError{code: 0, err: fmt.Errorf("TestJmapApiClient: unsupported jmap command: %v", command)}
|
|
}
|
|
}
|
|
|
|
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
|
|
var seededRand *rand.Rand = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
func generateRandomString(length int) string {
|
|
b := make([]byte, length)
|
|
for i := range b {
|
|
b[i] = charset[seededRand.Intn(len(charset))]
|
|
}
|
|
return string(b)
|
|
}
|
|
|
|
func TestRequests(t *testing.T) {
|
|
require := require.New(t)
|
|
apiClient := NewTestJmapApiClient(t)
|
|
wkClient := NewTestJmapWellKnownClient(t)
|
|
blobClient := NewTestJmapBlobClient(t)
|
|
logger := log.NopLogger()
|
|
ctx := context.Background()
|
|
client := NewClient(wkClient, apiClient, blobClient)
|
|
|
|
jmapUrl, err := url.Parse("http://localhost/jmap")
|
|
require.NoError(err)
|
|
|
|
session := Session{Username: "user123", JmapUrl: *jmapUrl}
|
|
|
|
foldersByAccountId, sessionState, err := client.GetAllMailboxes([]string{"a"}, &session, ctx, &logger)
|
|
require.NoError(err)
|
|
require.Len(foldersByAccountId, 1)
|
|
require.Contains(foldersByAccountId, "a")
|
|
folders := foldersByAccountId["a"]
|
|
require.Len(folders.Mailboxes, 5)
|
|
require.NotEmpty(sessionState)
|
|
|
|
emails, sessionState, err := client.GetAllEmailsInMailbox("a", &session, ctx, &logger, "Inbox", 0, 0, true, 0)
|
|
require.NoError(err)
|
|
require.Len(emails.Emails, 3)
|
|
require.NotEmpty(sessionState)
|
|
|
|
{
|
|
email := emails.Emails[0]
|
|
require.Equal("Ornare Senectus Ultrices Elit", email.Subject)
|
|
require.Equal(false, email.HasAttachment)
|
|
}
|
|
{
|
|
email := emails.Emails[1]
|
|
require.Equal("Lorem Tortor Eros Blandit Adipiscing Scelerisque Fermentum", email.Subject)
|
|
require.Equal(false, email.HasAttachment)
|
|
}
|
|
}
|
|
|
|
func TestEmailFilterSerialization(t *testing.T) {
|
|
expectedFilterJson := `
|
|
{"operator":"AND","conditions":[{"hasKeyword":"seen","text":"sample"},{"hasKeyword":"draft"}]}
|
|
`
|
|
|
|
require := require.New(t)
|
|
|
|
text := "sample"
|
|
mailboxId := ""
|
|
notInMailboxIds := []string{}
|
|
from := ""
|
|
to := ""
|
|
cc := ""
|
|
bcc := ""
|
|
subject := ""
|
|
body := ""
|
|
before := time.Time{}
|
|
after := time.Time{}
|
|
minSize := 0
|
|
maxSize := 0
|
|
keywords := []string{"seen", "draft"}
|
|
|
|
var filter EmailFilterElement
|
|
|
|
firstFilter := EmailFilterCondition{
|
|
Text: text,
|
|
InMailbox: mailboxId,
|
|
InMailboxOtherThan: notInMailboxIds,
|
|
From: from,
|
|
To: to,
|
|
Cc: cc,
|
|
Bcc: bcc,
|
|
Subject: subject,
|
|
Body: body,
|
|
Before: before,
|
|
After: after,
|
|
MinSize: minSize,
|
|
MaxSize: maxSize,
|
|
}
|
|
filter = &firstFilter
|
|
|
|
if len(keywords) > 0 {
|
|
firstFilter.HasKeyword = keywords[0]
|
|
if len(keywords) > 1 {
|
|
firstFilter.HasKeyword = keywords[0]
|
|
filters := make([]EmailFilterElement, len(keywords))
|
|
filters[0] = firstFilter
|
|
for i, keyword := range keywords[1:] {
|
|
filters[i+1] = EmailFilterCondition{
|
|
HasKeyword: keyword,
|
|
}
|
|
}
|
|
filter = &EmailFilterOperator{
|
|
Operator: And,
|
|
Conditions: filters,
|
|
}
|
|
}
|
|
}
|
|
|
|
b, err := json.Marshal(filter)
|
|
require.NoError(err)
|
|
json := string(b)
|
|
require.Equal(strings.TrimSpace(expectedFilterJson), json)
|
|
}
|