2022-06-14 13:27:09 +01:00
|
|
|
package carddav
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/emersion/go-vcard"
|
|
|
|
"github.com/emersion/go-webdav"
|
|
|
|
)
|
|
|
|
|
2024-02-23 11:27:32 +00:00
|
|
|
type testBackend struct {
|
|
|
|
addressBooks []AddressBook
|
|
|
|
}
|
2022-06-14 13:27:09 +01:00
|
|
|
|
|
|
|
type contextKey string
|
|
|
|
|
|
|
|
var (
|
|
|
|
aliceData = `BEGIN:VCARD
|
|
|
|
VERSION:4.0
|
|
|
|
UID:urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1
|
|
|
|
FN;PID=1.1:Alice Gopher
|
|
|
|
N:Gopher;Alice;;;
|
|
|
|
EMAIL;PID=1.1:alice@example.com
|
|
|
|
CLIENTPIDMAP:1;urn:uuid:53e374d9-337e-4727-8803-a1e9c14e0551
|
|
|
|
END:VCARD`
|
|
|
|
alicePath = "urn:uuid:4fbe8971-0bc3-424c-9c26-36c3e1eff6b1.vcf"
|
|
|
|
|
2022-11-15 20:25:37 +00:00
|
|
|
currentUserPrincipalKey = contextKey("test:currentUserPrincipal")
|
|
|
|
homeSetPathKey = contextKey("test:homeSetPath")
|
|
|
|
addressBookPathKey = contextKey("test:addressBookPath")
|
2022-06-14 13:27:09 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
func (*testBackend) CurrentUserPrincipal(ctx context.Context) (string, error) {
|
|
|
|
r := ctx.Value(currentUserPrincipalKey).(string)
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2024-02-02 15:09:49 +00:00
|
|
|
func (*testBackend) AddressBookHomeSetPath(ctx context.Context) (string, error) {
|
2022-06-14 13:27:09 +01:00
|
|
|
r := ctx.Value(homeSetPathKey).(string)
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2024-02-02 15:09:49 +00:00
|
|
|
func (*testBackend) ListAddressBooks(ctx context.Context) ([]AddressBook, error) {
|
2022-06-14 13:27:09 +01:00
|
|
|
p := ctx.Value(addressBookPathKey).(string)
|
2024-02-02 15:09:49 +00:00
|
|
|
return []AddressBook{
|
|
|
|
AddressBook{
|
|
|
|
Path: p,
|
|
|
|
Name: "My contacts",
|
|
|
|
Description: "Default address book",
|
|
|
|
MaxResourceSize: 1024,
|
|
|
|
SupportedAddressData: nil,
|
|
|
|
},
|
2022-06-14 13:27:09 +01:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2024-02-02 15:09:49 +00:00
|
|
|
func (b *testBackend) GetAddressBook(ctx context.Context, path string) (*AddressBook, error) {
|
|
|
|
abs, err := b.ListAddressBooks(ctx)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
for _, ab := range abs {
|
|
|
|
if ab.Path == path {
|
|
|
|
return &ab, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found"))
|
|
|
|
}
|
|
|
|
|
2024-02-23 11:27:32 +00:00
|
|
|
func (b *testBackend) CreateAddressBook(ctx context.Context, ab *AddressBook) error {
|
|
|
|
b.addressBooks = append(b.addressBooks, *ab)
|
|
|
|
return nil
|
2024-02-07 10:47:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (*testBackend) DeleteAddressBook(ctx context.Context, path string) error {
|
|
|
|
panic("TODO: implement")
|
|
|
|
}
|
|
|
|
|
2022-06-14 13:27:09 +01:00
|
|
|
func (*testBackend) GetAddressObject(ctx context.Context, path string, req *AddressDataRequest) (*AddressObject, error) {
|
|
|
|
if path == alicePath {
|
|
|
|
card, err := vcard.NewDecoder(strings.NewReader(aliceData)).Decode()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &AddressObject{
|
|
|
|
Path: path,
|
|
|
|
Card: card,
|
|
|
|
}, nil
|
|
|
|
} else {
|
|
|
|
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found"))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-02 15:09:49 +00:00
|
|
|
func (b *testBackend) ListAddressObjects(ctx context.Context, path string, req *AddressDataRequest) ([]AddressObject, error) {
|
|
|
|
p := ctx.Value(addressBookPathKey).(string)
|
|
|
|
if !strings.HasPrefix(path, p) {
|
|
|
|
return nil, webdav.NewHTTPError(404, fmt.Errorf("Not found"))
|
|
|
|
}
|
2022-06-14 13:27:09 +01:00
|
|
|
alice, err := b.GetAddressObject(ctx, alicePath, req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return []AddressObject{*alice}, nil
|
|
|
|
}
|
|
|
|
|
2024-02-02 15:09:49 +00:00
|
|
|
func (*testBackend) QueryAddressObjects(ctx context.Context, path string, query *AddressBookQuery) ([]AddressObject, error) {
|
2022-06-14 13:27:09 +01:00
|
|
|
panic("TODO: implement")
|
|
|
|
}
|
|
|
|
|
2024-02-20 14:54:28 +00:00
|
|
|
func (*testBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (*AddressObject, error) {
|
2022-06-14 13:27:09 +01:00
|
|
|
panic("TODO: implement")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (*testBackend) DeleteAddressObject(ctx context.Context, path string) error {
|
|
|
|
panic("TODO: implement")
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestAddressBookDiscovery(t *testing.T) {
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
2022-08-24 10:52:11 +01:00
|
|
|
prefix string
|
2022-06-14 13:27:09 +01:00
|
|
|
currentUserPrincipal string
|
|
|
|
homeSetPath string
|
|
|
|
addressBookPath string
|
|
|
|
}{
|
|
|
|
{
|
2022-08-24 10:52:11 +01:00
|
|
|
name: "simple",
|
|
|
|
prefix: "",
|
2022-06-14 13:27:09 +01:00
|
|
|
currentUserPrincipal: "/test/",
|
|
|
|
homeSetPath: "/test/contacts/",
|
|
|
|
addressBookPath: "/test/contacts/private",
|
|
|
|
},
|
2022-08-24 10:52:11 +01:00
|
|
|
{
|
|
|
|
name: "prefix",
|
|
|
|
prefix: "/dav",
|
|
|
|
currentUserPrincipal: "/dav/test/",
|
|
|
|
homeSetPath: "/dav/test/contacts/",
|
|
|
|
addressBookPath: "/dav/test/contacts/private",
|
|
|
|
},
|
2022-06-14 13:27:09 +01:00
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
2023-12-13 13:37:38 +00:00
|
|
|
ctx := context.Background()
|
2022-06-14 13:27:09 +01:00
|
|
|
|
2022-08-24 10:52:11 +01:00
|
|
|
h := Handler{&testBackend{}, tc.prefix}
|
2022-06-14 13:27:09 +01:00
|
|
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ctx := r.Context()
|
|
|
|
ctx = context.WithValue(ctx, currentUserPrincipalKey, tc.currentUserPrincipal)
|
|
|
|
ctx = context.WithValue(ctx, homeSetPathKey, tc.homeSetPath)
|
|
|
|
ctx = context.WithValue(ctx, addressBookPathKey, tc.addressBookPath)
|
|
|
|
r = r.WithContext(ctx)
|
|
|
|
(&h).ServeHTTP(w, r)
|
|
|
|
}))
|
|
|
|
defer ts.Close()
|
|
|
|
|
|
|
|
// client supports .well-known discovery if explicitly pointed to it
|
|
|
|
startURL := ts.URL
|
|
|
|
if tc.currentUserPrincipal != "/" {
|
|
|
|
startURL = ts.URL + "/.well-known/carddav"
|
|
|
|
}
|
|
|
|
|
|
|
|
client, err := NewClient(nil, startURL)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating client: %s", err)
|
|
|
|
}
|
2023-12-13 13:37:38 +00:00
|
|
|
cup, err := client.FindCurrentUserPrincipal(ctx)
|
2022-06-14 13:27:09 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error finding user principal url: %s", err)
|
|
|
|
}
|
|
|
|
if cup != tc.currentUserPrincipal {
|
|
|
|
t.Fatalf("Found current user principal URL '%s', expected '%s'", cup, tc.currentUserPrincipal)
|
|
|
|
}
|
2023-12-13 13:37:38 +00:00
|
|
|
hsp, err := client.FindAddressBookHomeSet(ctx, cup)
|
2022-06-14 13:27:09 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error finding home set path: %s", err)
|
|
|
|
}
|
|
|
|
if hsp != tc.homeSetPath {
|
|
|
|
t.Fatalf("Found home set path '%s', expected '%s'", hsp, tc.homeSetPath)
|
|
|
|
}
|
2023-12-13 13:37:38 +00:00
|
|
|
abs, err := client.FindAddressBooks(ctx, hsp)
|
2022-06-14 13:27:09 +01:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error finding address books: %s", err)
|
|
|
|
}
|
|
|
|
if len(abs) != 1 {
|
|
|
|
t.Fatalf("Found %d address books, expected 1", len(abs))
|
|
|
|
}
|
|
|
|
if abs[0].Path != tc.addressBookPath {
|
|
|
|
t.Fatalf("Found address book at %s, expected %s", abs[0].Path, tc.addressBookPath)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2024-02-23 11:27:32 +00:00
|
|
|
|
|
|
|
var mkcolRequestBody = `
|
|
|
|
<?xml version="1.0" encoding="utf-8" ?>
|
|
|
|
<D:mkcol xmlns:D="DAV:"
|
|
|
|
xmlns:C="urn:ietf:params:xml:ns:carddav">
|
|
|
|
<D:set>
|
|
|
|
<D:prop>
|
|
|
|
<D:resourcetype>
|
|
|
|
<D:collection/>
|
|
|
|
<C:addressbook/>
|
|
|
|
</D:resourcetype>
|
|
|
|
<D:displayname>Lisa's Contacts</D:displayname>
|
|
|
|
<C:addressbook-description xml:lang="en"
|
|
|
|
>My primary address book.</C:addressbook-description>
|
|
|
|
</D:prop>
|
|
|
|
</D:set>
|
|
|
|
</D:mkcol>`
|
|
|
|
|
|
|
|
func TestCreateAddressbookMinimalBody(t *testing.T) {
|
|
|
|
tb := testBackend{
|
|
|
|
addressBooks: nil,
|
|
|
|
}
|
|
|
|
b := backend{
|
|
|
|
Backend: &tb,
|
|
|
|
Prefix: "/dav",
|
|
|
|
}
|
|
|
|
req := httptest.NewRequest("MKCOL", "/dav/addressbooks/user0/test-addressbook", strings.NewReader(mkcolRequestBody))
|
|
|
|
req.Header.Set("Content-Type", "application/xml")
|
|
|
|
|
|
|
|
err := b.Mkcol(req)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Unexpcted error in Mkcol: %s", err)
|
|
|
|
}
|
|
|
|
if len(tb.addressBooks) != 1 {
|
|
|
|
t.Fatalf("Found %d address books, expected 1", len(tb.addressBooks))
|
|
|
|
}
|
|
|
|
c := tb.addressBooks[0]
|
|
|
|
if c.Name != "Lisa's Contacts" {
|
|
|
|
t.Fatalf("Address book name is '%s', expected 'Lisa's Contacts'", c.Name)
|
|
|
|
}
|
|
|
|
if c.Path != "/dav/addressbooks/user0/test-addressbook" {
|
|
|
|
t.Fatalf("Address book path is '%s', expected '/dav/addressbooks/user0/test-addressbook'", c.Path)
|
|
|
|
}
|
|
|
|
if c.Description != "My primary address book." {
|
|
|
|
t.Fatalf("Address book sdscription is '%s', expected 'My primary address book.'", c.Description)
|
|
|
|
}
|
|
|
|
}
|