diff --git a/carddav/carddav_test.go b/carddav/carddav_test.go new file mode 100644 index 0000000..0377a9f --- /dev/null +++ b/carddav/carddav_test.go @@ -0,0 +1,174 @@ +package carddav + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/emersion/go-vcard" + "github.com/emersion/go-webdav" +) + +type testBackend struct{} + +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" + + currentUserPrincipalKey = "test:currentUserPrincipal" + homeSetPathKey = "test:homeSetPath" + addressBookPathKey = "test:addressBookPath" +) + +func (*testBackend) CurrentUserPrincipal(ctx context.Context) (string, error) { + r := ctx.Value(currentUserPrincipalKey).(string) + return r, nil +} + +func (*testBackend) AddressbookHomeSetPath(ctx context.Context) (string, error) { + r := ctx.Value(homeSetPathKey).(string) + return r, nil +} + +func (*testBackend) AddressBook(ctx context.Context) (*AddressBook, error) { + p := ctx.Value(addressBookPathKey).(string) + return &AddressBook{ + Path: p, + Name: "My contacts", + Description: "Default address book", + MaxResourceSize: 1024, + SupportedAddressData: nil, + }, nil +} + +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")) + } +} + +func (b *testBackend) ListAddressObjects(ctx context.Context, req *AddressDataRequest) ([]AddressObject, error) { + alice, err := b.GetAddressObject(ctx, alicePath, req) + if err != nil { + return nil, err + } + + return []AddressObject{*alice}, nil +} + +func (*testBackend) QueryAddressObjects(ctx context.Context, query *AddressBookQuery) ([]AddressObject, error) { + panic("TODO: implement") +} + +func (*testBackend) PutAddressObject(ctx context.Context, path string, card vcard.Card, opts *PutAddressObjectOptions) (loc string, err error) { + 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 + currentUserPrincipal string + homeSetPath string + addressBookPath string + }{ + // TODO this used to work, but is currently broken. + //{ + // name: "all-at-root", + // currentUserPrincipal: "/", + // homeSetPath: "/", + // addressBookPath: "/", + //}, + { + name: "simple-home-set-path", + currentUserPrincipal: "/", + homeSetPath: "/contacts/", + addressBookPath: "/contacts/", + }, + { + name: "all-at-different-paths", + currentUserPrincipal: "/", + homeSetPath: "/contacts/", + addressBookPath: "/contacts/work", + }, + { + name: "nothing-at-root", + currentUserPrincipal: "/test/", + homeSetPath: "/test/contacts/", + addressBookPath: "/test/contacts/private", + }, + } { + t.Run(tc.name, func(t *testing.T) { + + h := Handler{&testBackend{}} + 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) + } + cup, err := client.FindCurrentUserPrincipal() + 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) + } + hsp, err := client.FindAddressBookHomeSet(cup) + 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) + } + abs, err := client.FindAddressBooks(hsp) + 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) + } + }) + } +}