From 4a3cd0510f590534e8d1d69c70f736f6ca9762b3 Mon Sep 17 00:00:00 2001 From: Conrad Hoffmann Date: Tue, 14 Jun 2022 14:27:09 +0200 Subject: [PATCH] carddav: end-to-end test address book discovery As the implementation evolves, it will be necessary to have more tests to assert we don't break anything when making changes. This commit introduces a test setup to test that server and client can handle the address book discovery with various parameters. The test setup should be easily extendable to cover even more ground as needed. --- carddav/carddav_test.go | 174 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 carddav/carddav_test.go 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) + } + }) + } +}