package selectors

import (
	"strings"
	"testing"
	"time"

	"github.com/alcionai/clues"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"github.com/alcionai/corso/src/internal/tester"
	"github.com/alcionai/corso/src/pkg/backup/details"
	"github.com/alcionai/corso/src/pkg/dttm"
	"github.com/alcionai/corso/src/pkg/fault"
	"github.com/alcionai/corso/src/pkg/filters"
	"github.com/alcionai/corso/src/pkg/path"
)

type ExchangeSelectorSuite struct {
	tester.Suite
}

func TestExchangeSelectorSuite(t *testing.T) {
	suite.Run(t, &ExchangeSelectorSuite{Suite: tester.NewUnitSuite(t)})
}

func (suite *ExchangeSelectorSuite) TestNewExchangeBackup() {
	t := suite.T()
	eb := NewExchangeBackup(nil)
	assert.Equal(t, eb.Service, ServiceExchange)
	assert.NotZero(t, eb.Scopes())
}

func (suite *ExchangeSelectorSuite) TestToExchangeBackup() {
	t := suite.T()
	eb := NewExchangeBackup(nil)
	s := eb.Selector
	eb, err := s.ToExchangeBackup()
	require.NoError(t, err, clues.ToCore(err))
	assert.Equal(t, eb.Service, ServiceExchange)
	assert.NotZero(t, eb.Scopes())
}

func (suite *ExchangeSelectorSuite) TestNewExchangeRestore() {
	t := suite.T()
	er := NewExchangeRestore(nil)
	assert.Equal(t, er.Service, ServiceExchange)
	assert.NotZero(t, er.Scopes())
}

func (suite *ExchangeSelectorSuite) TestToExchangeRestore() {
	t := suite.T()
	eb := NewExchangeRestore(nil)
	s := eb.Selector
	eb, err := s.ToExchangeRestore()
	require.NoError(t, err, clues.ToCore(err))
	assert.Equal(t, eb.Service, ServiceExchange)
	assert.NotZero(t, eb.Scopes())
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Contacts() {
	t := suite.T()

	const (
		user   = "user"
		folder = AnyTgt
		c1     = "c1"
		c2     = "c2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.Contacts([]string{folder}, []string{c1, c2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeContactFolder: {folder},
			ExchangeContact:       {c1, c2},
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Contacts() {
	t := suite.T()

	const (
		user   = "user"
		folder = AnyTgt
		c1     = "c1"
		c2     = "c2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.Contacts([]string{folder}, []string{c1, c2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeContactFolder: {folder},
			ExchangeContact:       {c1, c2},
		})

	assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContact)
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_ContactFolders() {
	t := suite.T()

	const (
		user = "user"
		f1   = "f1"
		f2   = "f2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.ContactFolders([]string{f1, f2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeContactFolder: {f1, f2},
			ExchangeContact:       Any(),
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_ContactFolders() {
	t := suite.T()

	const (
		user = "user"
		f1   = "f1"
		f2   = "f2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.ContactFolders([]string{f1, f2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeContactFolder: {f1, f2},
			ExchangeContact:       Any(),
		})

	assert.Equal(t, sel.Scopes()[0].Category(), ExchangeContactFolder)
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Events() {
	t := suite.T()

	const (
		user = "user"
		e1   = "e1"
		e2   = "e2"
		c1   = "c1"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.Events([]string{c1}, []string{e1, e2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeEventCalendar: {c1},
			ExchangeEvent:         {e1, e2},
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_EventCalendars() {
	t := suite.T()

	const (
		user = "user"
		c1   = "c1"
		c2   = "c2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.EventCalendars([]string{c1, c2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeEventCalendar: {c1, c2},
			ExchangeEvent:         Any(),
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Events() {
	t := suite.T()

	const (
		user = "user"
		e1   = "e1"
		e2   = "e2"
		c1   = "c1"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.Events([]string{c1}, []string{e1, e2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeEventCalendar: {c1},
			ExchangeEvent:         {e1, e2},
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_EventCalendars() {
	t := suite.T()

	const (
		user = "user"
		c1   = "c1"
		c2   = "c2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.EventCalendars([]string{c1, c2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeEventCalendar: {c1, c2},
			ExchangeEvent:         Any(),
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_Mails() {
	t := suite.T()

	const (
		user   = "user"
		folder = AnyTgt
		m1     = "m1"
		m2     = "m2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.Mails([]string{folder}, []string{m1, m2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeMailFolder: {folder},
			ExchangeMail:       {m1, m2},
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_Mails() {
	t := suite.T()

	const (
		user   = "user"
		folder = AnyTgt
		m1     = "m1"
		m2     = "m2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.Mails([]string{folder}, []string{m1, m2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeMailFolder: {folder},
			ExchangeMail:       {m1, m2},
		})

	assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMail)
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_MailFolders() {
	t := suite.T()

	const (
		user = "user"
		f1   = "f1"
		f2   = "f2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Exclude(sel.MailFolders([]string{f1, f2}))
	scopes := sel.Excludes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeMailFolder: {f1, f2},
			ExchangeMail:       Any(),
		})
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_MailFolders() {
	t := suite.T()

	const (
		user = "user"
		f1   = "f1"
		f2   = "f2"
	)

	sel := NewExchangeBackup([]string{user})
	sel.Include(sel.MailFolders([]string{f1, f2}))
	scopes := sel.Includes
	require.Len(t, scopes, 1)

	scopeMustHave(
		t,
		ExchangeScope(scopes[0]),
		map[categorizer][]string{
			ExchangeMailFolder: {f1, f2},
			ExchangeMail:       Any(),
		})

	assert.Equal(t, sel.Scopes()[0].Category(), ExchangeMailFolder)
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Exclude_AllData() {
	t := suite.T()

	const (
		u1 = "u1"
		u2 = "u2"
	)

	sel := NewExchangeBackup([]string{u1, u2})
	sel.Exclude(sel.AllData())
	scopes := sel.Excludes
	require.Len(t, scopes, 3)

	for _, sc := range scopes {
		if sc[scopeKeyCategory].Compare(ExchangeContactFolder.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeContact:       Any(),
					ExchangeContactFolder: Any(),
				})
		}

		if sc[scopeKeyCategory].Compare(ExchangeEvent.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeEvent: Any(),
				})
		}

		if sc[scopeKeyCategory].Compare(ExchangeMailFolder.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeMail:       Any(),
					ExchangeMailFolder: Any(),
				})
		}
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeSelector_Include_AllData() {
	t := suite.T()

	const (
		u1 = "u1"
		u2 = "u2"
	)

	sel := NewExchangeBackup([]string{u1, u2})
	sel.Include(sel.AllData())
	scopes := sel.Includes
	require.Len(t, scopes, 3)

	for _, sc := range scopes {
		if sc[scopeKeyCategory].Compare(ExchangeContactFolder.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeContact:       Any(),
					ExchangeContactFolder: Any(),
				})
		}

		if sc[scopeKeyCategory].Compare(ExchangeEvent.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeEvent: Any(),
				})
		}

		if sc[scopeKeyCategory].Compare(ExchangeMailFolder.String()) {
			scopeMustHave(
				t,
				ExchangeScope(sc),
				map[categorizer][]string{
					ExchangeMail:       Any(),
					ExchangeMailFolder: Any(),
				})
		}
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeBackup_Scopes() {
	eb := NewExchangeBackup(Any())
	eb.Include(eb.AllData())

	scopes := eb.Scopes()
	assert.Len(suite.T(), scopes, 3)

	for _, sc := range scopes {
		cat := sc.Category()
		suite.Run(cat.String(), func() {
			t := suite.T()

			switch sc.Category() {
			case ExchangeContactFolder:
				assert.True(t, sc.IsAny(ExchangeContact))
				assert.True(t, sc.IsAny(ExchangeContactFolder))
			case ExchangeEvent:
				assert.True(t, sc.IsAny(ExchangeEvent))
			case ExchangeMailFolder:
				assert.True(t, sc.IsAny(ExchangeMail))
				assert.True(t, sc.IsAny(ExchangeMailFolder))
			}
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeScope_Category() {
	table := []struct {
		is     exchangeCategory
		expect exchangeCategory
		check  assert.ComparisonAssertionFunc
	}{
		{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.Equal},
		{ExchangeCategoryUnknown, ExchangeUser, assert.NotEqual},
		{ExchangeContact, ExchangeContact, assert.Equal},
		{ExchangeContact, ExchangeMailFolder, assert.NotEqual},
		{ExchangeContactFolder, ExchangeContactFolder, assert.Equal},
		{ExchangeContactFolder, ExchangeMailFolder, assert.NotEqual},
		{ExchangeEvent, ExchangeEvent, assert.Equal},
		{ExchangeEvent, ExchangeEventCalendar, assert.NotEqual},
		{ExchangeEventCalendar, ExchangeEventCalendar, assert.Equal},
		{ExchangeEvent, ExchangeContact, assert.NotEqual},
		{ExchangeMail, ExchangeMail, assert.Equal},
		{ExchangeMail, ExchangeMailFolder, assert.NotEqual},
		{ExchangeMailFolder, ExchangeMailFolder, assert.Equal},
		{ExchangeMailFolder, ExchangeContactFolder, assert.NotEqual},
		{ExchangeUser, ExchangeUser, assert.Equal},
		{ExchangeUser, ExchangeCategoryUnknown, assert.NotEqual},
	}
	for _, test := range table {
		suite.Run(test.is.String()+test.expect.String(), func() {
			eb := NewExchangeBackup(Any())
			eb.Includes = []scope{
				{scopeKeyCategory: filters.Identity(test.is.String())},
			}
			scope := eb.Scopes()[0]
			test.check(suite.T(), test.expect, scope.Category())
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeScope_IncludesCategory() {
	table := []struct {
		is     exchangeCategory
		expect exchangeCategory
		check  assert.BoolAssertionFunc
	}{
		{ExchangeCategoryUnknown, ExchangeCategoryUnknown, assert.False},
		{ExchangeCategoryUnknown, ExchangeUser, assert.True},
		{ExchangeContact, ExchangeContactFolder, assert.True},
		{ExchangeContact, ExchangeMailFolder, assert.False},
		{ExchangeContactFolder, ExchangeContact, assert.True},
		{ExchangeContactFolder, ExchangeMailFolder, assert.False},
		{ExchangeContactFolder, ExchangeEventCalendar, assert.False},
		{ExchangeEvent, ExchangeUser, assert.True},
		{ExchangeEvent, ExchangeContact, assert.False},
		{ExchangeEvent, ExchangeEventCalendar, assert.True},
		{ExchangeEvent, ExchangeContactFolder, assert.False},
		{ExchangeEventCalendar, ExchangeEvent, assert.True},
		{ExchangeEventCalendar, ExchangeEventCalendar, assert.True},
		{ExchangeEventCalendar, ExchangeUser, assert.True},
		{ExchangeEventCalendar, ExchangeCategoryUnknown, assert.False},
		{ExchangeMail, ExchangeMailFolder, assert.True},
		{ExchangeMail, ExchangeContact, assert.False},
		{ExchangeMailFolder, ExchangeMail, assert.True},
		{ExchangeMailFolder, ExchangeContactFolder, assert.False},
		{ExchangeMailFolder, ExchangeEventCalendar, assert.False},
		{ExchangeUser, ExchangeUser, assert.True},
		{ExchangeUser, ExchangeCategoryUnknown, assert.True},
		{ExchangeUser, ExchangeMail, assert.True},
		{ExchangeUser, ExchangeEventCalendar, assert.True},
	}
	for _, test := range table {
		suite.Run(test.is.String()+test.expect.String(), func() {
			eb := NewExchangeBackup(Any())
			eb.Includes = []scope{
				{scopeKeyCategory: filters.Identity(test.is.String())},
			}
			scope := eb.Scopes()[0]
			test.check(suite.T(), scope.IncludesCategory(test.expect))
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeScope_Get() {
	eb := NewExchangeBackup(Any())
	eb.Include(eb.AllData())

	scopes := eb.Scopes()

	table := []exchangeCategory{
		ExchangeContact,
		ExchangeContactFolder,
		ExchangeEvent,
		ExchangeMail,
		ExchangeMailFolder,
	}
	for _, test := range table {
		suite.Run(test.String(), func() {
			t := suite.T()

			for _, sc := range scopes {
				switch sc.Category() {
				case ExchangeContactFolder:
					assert.Equal(t, Any(), sc.Get(ExchangeContact))
					assert.Equal(t, Any(), sc.Get(ExchangeContactFolder))
				case ExchangeEvent:
					assert.Equal(t, Any(), sc.Get(ExchangeEvent))
				case ExchangeMailFolder:
					assert.Equal(t, Any(), sc.Get(ExchangeMail))
					assert.Equal(t, Any(), sc.Get(ExchangeMailFolder))
				}
				assert.Equal(t, None(), sc.Get(ExchangeCategoryUnknown))
			}
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesInfo() {
	es := NewExchangeRestore(Any())

	const (
		name      = "smarf mcfnords"
		organizer = "cooks@2many.smarf"
		sender    = "smarf@2many.cooks"
		subject   = "I have seen the fnords!"
	)

	var (
		epoch  = time.Time{}
		now    = time.Now()
		future = now.Add(1 * time.Minute)
	)

	infoWith := func(itype details.ItemType) details.ItemInfo {
		return details.ItemInfo{
			Exchange: &details.ExchangeInfo{
				ItemType:    itype,
				ContactName: name,
				EventRecurs: true,
				EventStart:  now,
				Organizer:   organizer,
				Sender:      sender,
				Subject:     subject,
				Received:    now,
			},
		}
	}

	table := []struct {
		name   string
		itype  details.ItemType
		scope  []ExchangeScope
		expect assert.BoolAssertionFunc
	}{
		{"any mail with a sender", details.ExchangeMail, es.MailSender(AnyTgt), assert.True},
		{"no mail, regardless of sender", details.ExchangeMail, es.MailSender(NoneTgt), assert.False},
		{"mail from a different sender", details.ExchangeMail, es.MailSender("magoo@ma.goo"), assert.False},
		{"mail from the matching sender", details.ExchangeMail, es.MailSender(sender), assert.True},
		{"mail with any subject", details.ExchangeMail, es.MailSubject(AnyTgt), assert.True},
		{"mail with none subject", details.ExchangeMail, es.MailSubject(NoneTgt), assert.False},
		{"mail with a different subject", details.ExchangeMail, es.MailSubject("fancy"), assert.False},
		{"mail with the matching subject", details.ExchangeMail, es.MailSubject(subject), assert.True},
		{"mail with a substring subject match", details.ExchangeMail, es.MailSubject(subject[5:9]), assert.True},
		{"mail received after the epoch", details.ExchangeMail, es.MailReceivedAfter(dttm.Format(epoch)), assert.True},
		{"mail received after now", details.ExchangeMail, es.MailReceivedAfter(dttm.Format(now)), assert.False},
		{
			"mail received after sometime later",
			details.ExchangeMail,
			es.MailReceivedAfter(dttm.Format(future)),
			assert.False,
		},
		{
			"mail received before the epoch",
			details.ExchangeMail,
			es.MailReceivedBefore(dttm.Format(epoch)),
			assert.False,
		},
		{"mail received before now", details.ExchangeMail, es.MailReceivedBefore(dttm.Format(now)), assert.False},
		{
			"mail received before sometime later",
			details.ExchangeMail,
			es.MailReceivedBefore(dttm.Format(future)),
			assert.True,
		},
		{"event with any organizer", details.ExchangeEvent, es.EventOrganizer(AnyTgt), assert.True},
		{"event with none organizer", details.ExchangeEvent, es.EventOrganizer(NoneTgt), assert.False},
		{"event with a different organizer", details.ExchangeEvent, es.EventOrganizer("fancy"), assert.False},
		{"event with the matching organizer", details.ExchangeEvent, es.EventOrganizer(organizer), assert.True},
		{"event that recurs", details.ExchangeEvent, es.EventRecurs("true"), assert.True},
		{"event that does not recur", details.ExchangeEvent, es.EventRecurs("false"), assert.False},
		{"event starting after the epoch", details.ExchangeEvent, es.EventStartsAfter(dttm.Format(epoch)), assert.True},
		{"event starting after now", details.ExchangeEvent, es.EventStartsAfter(dttm.Format(now)), assert.False},
		{
			"event starting after sometime later",
			details.ExchangeEvent,
			es.EventStartsAfter(dttm.Format(future)),
			assert.False,
		},
		{
			"event starting before the epoch",
			details.ExchangeEvent,
			es.EventStartsBefore(dttm.Format(epoch)),
			assert.False,
		},
		{"event starting before now", details.ExchangeEvent, es.EventStartsBefore(dttm.Format(now)), assert.False},
		{
			"event starting before sometime later",
			details.ExchangeEvent,
			es.EventStartsBefore(dttm.Format(future)),
			assert.True,
		},
		{"event with any subject", details.ExchangeEvent, es.EventSubject(AnyTgt), assert.True},
		{"event with none subject", details.ExchangeEvent, es.EventSubject(NoneTgt), assert.False},
		{"event with a different subject", details.ExchangeEvent, es.EventSubject("fancy"), assert.False},
		{"event with the matching subject", details.ExchangeEvent, es.EventSubject(subject), assert.True},
		{"contact with a different name", details.ExchangeContact, es.ContactName("blarps"), assert.False},
		{"contact with the same name", details.ExchangeContact, es.ContactName(name), assert.True},
		{"contact with a subname search", details.ExchangeContact, es.ContactName(name[2:5]), assert.True},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			scopes := setScopesToDefault(test.scope)
			for _, scope := range scopes {
				test.expect(t, scope.matchesInfo(infoWith(test.itype)))
			}
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeScope_MatchesPath() {
	const (
		usr  = "userID"
		fID1 = "mf_id_1.d"
		fld1 = "mailFolder"
		fID2 = "mf_id_2.d"
		fld2 = "subFolder"
		mail = "mailID"
	)

	var (
		repo  = stubPath(suite.T(), usr, []string{fID1, fID2, mail}, path.EmailCategory)
		loc   = strings.Join([]string{fld1, fld2, mail}, "/")
		short = "thisisahashofsomekind"
		es    = NewExchangeRestore(Any())
		ent   = details.Entry{
			RepoRef:     repo.String(),
			ShortRef:    short,
			ItemRef:     mail,
			LocationRef: loc,
		}
	)

	table := []struct {
		name     string
		scope    []ExchangeScope
		shortRef string
		expect   assert.BoolAssertionFunc
	}{
		{"all items", es.AllData(), "", assert.True},
		{"all folders", es.MailFolders(Any()), "", assert.True},
		{"no folders", es.MailFolders(None()), "", assert.False},
		{"matching folder", es.MailFolders([]string{fld1}), "", assert.True},
		{"matching folder id", es.MailFolders([]string{fID1}), "", assert.False},
		{"incomplete matching folder", es.MailFolders([]string{"mail"}), "", assert.False},
		{"incomplete matching folder ID", es.MailFolders([]string{"mf_id"}), "", assert.False},
		{"non-matching folder", es.MailFolders([]string{"smarf"}), "", assert.False},
		{"non-matching folder substring", es.MailFolders([]string{fld1 + "_suffix"}), "", assert.False},
		{"non-matching folder id substring", es.MailFolders([]string{fID1 + "_suffix"}), "", assert.False},
		{"matching folder prefix", es.MailFolders([]string{fld1}, PrefixMatch()), "", assert.True},
		{"matching folder ID prefix", es.MailFolders([]string{fID1}, PrefixMatch()), "", assert.False},
		{"incomplete folder prefix", es.MailFolders([]string{"mail"}, PrefixMatch()), "", assert.False},
		{"matching folder substring", es.MailFolders([]string{"Folder"}), "", assert.False},
		{"one of multiple folders", es.MailFolders([]string{"smarf", fld2}), "", assert.True},
		{"one of multiple folders by ID", es.MailFolders([]string{"smarf", fID2}), "", assert.False},
		{"all mail", es.Mails(Any(), Any()), "", assert.True},
		{"no mail", es.Mails(Any(), None()), "", assert.False},
		{"matching mail", es.Mails(Any(), []string{mail}), "", assert.True},
		{"non-matching mail", es.Mails(Any(), []string{"smarf"}), "", assert.False},
		{"one of multiple mails", es.Mails(Any(), []string{"smarf", mail}), "", assert.True},
		{"mail short ref", es.Mails(Any(), []string{short}), short, assert.True},
		{"non-leaf short ref", es.Mails([]string{short}, []string{"foo"}), short, assert.False},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			scopes := setScopesToDefault(test.scope)
			var aMatch bool
			for _, scope := range scopes {
				pvs, err := ExchangeMail.pathValues(repo, ent, Config{})
				require.NoError(t, err)

				if matchesPathValues(scope, ExchangeMail, pvs) {
					aMatch = true
					break
				}
			}
			test.expect(t, aMatch)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce() {
	var (
		contact = stubPath(
			suite.T(),
			"uid",
			[]string{"cfld", "cid"},
			path.ContactsCategory)
		event = stubPath(
			suite.T(),
			"uid",
			[]string{"efld", "eid"},
			path.EventsCategory)
		mail = stubPath(
			suite.T(),
			"uid",
			[]string{"mfld", "mid"},
			path.EmailCategory)
		contactInSubFolder = stubPath(
			suite.T(),
			"uid",
			[]string{"cfld1/cfld2", "cid"},
			path.ContactsCategory)
	)

	toRR := func(p path.Path) string {
		newElems := []string{}

		for _, e := range p.Folders() {
			newElems = append(newElems, e+".d")
		}

		joinedFldrs := strings.Join(newElems, "/")

		return stubRepoRef(p.Service(), p.Category(), p.ProtectedResource(), joinedFldrs, p.Item())
	}

	makeDeets := func(refs ...path.Path) *details.Details {
		deets := &details.Details{
			DetailsModel: details.DetailsModel{
				Entries: []details.Entry{},
			},
		}

		for _, r := range refs {
			itype := details.UnknownType

			switch r {
			case contact:
				itype = details.ExchangeContact
			case event:
				itype = details.ExchangeEvent
			case mail:
				itype = details.ExchangeMail
			}

			deets.Entries = append(deets.Entries, details.Entry{
				RepoRef: toRR(r),
				// Don't escape because we assume nice paths.
				LocationRef: r.Folder(false),
				ItemInfo: details.ItemInfo{
					Exchange: &details.ExchangeInfo{
						ItemType: itype,
					},
				},
			})
		}

		return deets
	}

	table := []struct {
		name         string
		deets        *details.Details
		makeSelector func() *ExchangeRestore
		expect       []string
	}{
		{
			"no refs",
			makeDeets(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{},
		},
		{
			"contact only",
			makeDeets(contact),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{toRR(contact)},
		},
		{
			"event only",
			makeDeets(event),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{toRR(event)},
		},
		{
			"mail only",
			makeDeets(mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{toRR(mail)},
		},
		{
			"all",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{toRR(contact), toRR(event), toRR(mail)},
		},
		{
			"only match contact",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Contacts([]string{"cfld"}, []string{"cid"}))
				return er
			},
			[]string{toRR(contact)},
		},
		{
			"only match contactInSubFolder",
			makeDeets(contactInSubFolder, contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.ContactFolders([]string{"cfld1/cfld2"}))
				return er
			},
			[]string{toRR(contactInSubFolder)},
		},
		{
			"only match contactInSubFolder by prefix",
			makeDeets(contactInSubFolder, contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.ContactFolders([]string{"cfld1/cfld2"}, PrefixMatch()))
				return er
			},
			[]string{toRR(contactInSubFolder)},
		},
		{
			"only match contactInSubFolder by leaf folder",
			makeDeets(contactInSubFolder, contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.ContactFolders([]string{"cfld2"}))
				return er
			},
			[]string{toRR(contactInSubFolder)},
		},
		{
			"only match event",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Events([]string{"efld"}, []string{"eid"}))
				return er
			},
			[]string{toRR(event)},
		},
		{
			"only match mail",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Mails([]string{"mfld"}, []string{"mid"}))
				return er
			},
			[]string{toRR(mail)},
		},
		{
			"exclude contact",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Contacts([]string{"cfld"}, []string{"cid"}))
				return er
			},
			[]string{toRR(event), toRR(mail)},
		},
		{
			"exclude event",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Events([]string{"efld"}, []string{"eid"}))
				return er
			},
			[]string{toRR(contact), toRR(mail)},
		},
		{
			"exclude mail",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Mails([]string{"mfld"}, []string{"mid"}))
				return er
			},
			[]string{toRR(contact), toRR(event)},
		},
		{
			"filter on mail subject",
			func() *details.Details {
				ds := makeDeets(mail)
				for i := range ds.Entries {
					ds.Entries[i].Exchange.Subject = "has a subject"
				}
				return ds
			}(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Filter(er.MailSubject("subj"))
				return er
			},
			[]string{toRR(mail)},
		},
		{
			"filter on mail subject multiple input categories",
			func() *details.Details {
				mds := makeDeets(mail)
				for i := range mds.Entries {
					mds.Entries[i].Exchange.Subject = "has a subject"
				}

				ds := makeDeets(contact, event)
				ds.Entries = append(ds.Entries, mds.Entries...)

				return ds
			}(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Filter(er.MailSubject("subj"))
				return er
			},
			[]string{toRR(mail)},
		},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			ctx, flush := tester.NewContext(t)
			defer flush()

			sel := test.makeSelector()
			results := sel.Reduce(ctx, test.deets, fault.New(true))
			paths := results.Paths()
			assert.Equal(t, test.expect, paths)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeRestore_Reduce_locationRef() {
	var (
		contact         = stubRepoRef(path.ExchangeService, path.ContactsCategory, "uid", "id5/id6", "cid")
		contactLocation = "conts/my_cont"
		event           = stubRepoRef(path.ExchangeService, path.EventsCategory, "uid", "id1/id2", "eid")
		eventLocation   = "cal/my_cal"
		mail            = stubRepoRef(path.ExchangeService, path.EmailCategory, "uid", "id3/id4", "mid")
		mailLocation    = "inbx/my_mail"
	)

	makeDeets := func(refs ...string) *details.Details {
		deets := &details.Details{
			DetailsModel: details.DetailsModel{
				Entries: []details.Entry{},
			},
		}

		for _, r := range refs {
			var (
				location string
				itype    = details.UnknownType
			)

			switch r {
			case contact:
				itype = details.ExchangeContact
				location = contactLocation
			case event:
				itype = details.ExchangeEvent
				location = eventLocation
			case mail:
				itype = details.ExchangeMail
				location = mailLocation
			}

			deets.Entries = append(deets.Entries, details.Entry{
				RepoRef:     r,
				LocationRef: location,
				ItemInfo: details.ItemInfo{
					Exchange: &details.ExchangeInfo{
						ItemType: itype,
					},
				},
			})
		}

		return deets
	}

	arr := func(s ...string) []string {
		return s
	}

	table := []struct {
		name         string
		deets        *details.Details
		makeSelector func() *ExchangeRestore
		expect       []string
	}{
		{
			"no refs",
			makeDeets(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			[]string{},
		},
		{
			"contact only",
			makeDeets(contact),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			arr(contact),
		},
		{
			"event only",
			makeDeets(event),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			arr(event),
		},
		{
			"mail only",
			makeDeets(mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			arr(mail),
		},
		{
			"all",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				return er
			},
			arr(contact, event, mail),
		},
		{
			"only match contact",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Contacts([]string{contactLocation}, []string{"cid"}))
				return er
			},
			arr(contact),
		},
		{
			"only match event",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Events([]string{eventLocation}, []string{"eid"}))
				return er
			},
			arr(event),
		},
		{
			"only match mail",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore([]string{"uid"})
				er.Include(er.Mails([]string{mailLocation}, []string{"mid"}))
				return er
			},
			arr(mail),
		},
		{
			"exclude contact",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Contacts([]string{contactLocation}, []string{"cid"}))
				return er
			},
			arr(event, mail),
		},
		{
			"exclude event",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Events([]string{eventLocation}, []string{"eid"}))
				return er
			},
			arr(contact, mail),
		},
		{
			"exclude mail",
			makeDeets(contact, event, mail),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Exclude(er.Mails([]string{mailLocation}, []string{"mid"}))
				return er
			},
			arr(contact, event),
		},
		{
			"filter on mail subject",
			func() *details.Details {
				ds := makeDeets(mail)
				for i := range ds.Entries {
					ds.Entries[i].Exchange.Subject = "has a subject"
				}
				return ds
			}(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Filter(er.MailSubject("subj"))
				return er
			},
			arr(mail),
		},
		{
			"filter on mail subject multiple input categories",
			func() *details.Details {
				mds := makeDeets(mail)
				for i := range mds.Entries {
					mds.Entries[i].Exchange.Subject = "has a subject"
				}

				ds := makeDeets(contact, event)
				ds.Entries = append(ds.Entries, mds.Entries...)

				return ds
			}(),
			func() *ExchangeRestore {
				er := NewExchangeRestore(Any())
				er.Include(er.AllData())
				er.Filter(er.MailSubject("subj"))
				return er
			},
			arr(mail),
		},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			ctx, flush := tester.NewContext(t)
			defer flush()

			sel := test.makeSelector()
			results := sel.Reduce(ctx, test.deets, fault.New(true))
			paths := results.Paths()
			assert.Equal(t, test.expect, paths)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestScopesByCategory() {
	var (
		es       = NewExchangeRestore(Any())
		allData  = es.AllData()
		contacts = es.ContactFolders(Any())
		events   = es.EventCalendars(Any())
		mail     = es.MailFolders(Any())
	)

	type expect struct {
		contact int
		event   int
		mail    int
	}

	type input []scope

	makeInput := func(es ...[]ExchangeScope) []scope {
		mss := []scope{}

		for _, sl := range es {
			for _, s := range sl {
				mss = append(mss, scope(s))
			}
		}

		return mss
	}
	cats := map[path.CategoryType]exchangeCategory{
		path.ContactsCategory: ExchangeContact,
		path.EventsCategory:   ExchangeEvent,
		path.EmailCategory:    ExchangeMail,
	}

	table := []struct {
		name   string
		scopes input
		expect expect
	}{
		{"contacts only", makeInput(contacts), expect{1, 0, 0}},
		{"events only", makeInput(events), expect{0, 1, 0}},
		{"mail only", makeInput(mail), expect{0, 0, 1}},
		{"all", makeInput(allData, contacts, events, mail), expect{2, 2, 2}},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			result := scopesByCategory[ExchangeScope](test.scopes, cats, false)
			assert.Len(t, result[ExchangeContact], test.expect.contact)
			assert.Len(t, result[ExchangeEvent], test.expect.event)
			assert.Len(t, result[ExchangeMail], test.expect.mail)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestPasses() {
	const (
		mid = "mailID"
		cat = ExchangeMail
	)

	short := "thisisahashofsomekind"
	entry := details.Entry{
		ShortRef: short,
		ItemRef:  mid,
	}

	var (
		es        = NewExchangeRestore(Any())
		otherMail = setScopesToDefault(es.Mails(Any(), []string{"smarf"}))
		mail      = setScopesToDefault(es.Mails(Any(), []string{mid}))
		noMail    = setScopesToDefault(es.Mails(Any(), None()))
		allMail   = setScopesToDefault(es.Mails(Any(), Any()))
		repo      = stubPath(suite.T(), "user", []string{"folder", mid}, path.EmailCategory)
		ent       = details.Entry{
			RepoRef: repo.String(),
		}
	)

	table := []struct {
		name                        string
		excludes, filters, includes []ExchangeScope
		expect                      assert.BoolAssertionFunc
	}{
		{"empty", nil, nil, nil, assert.False},
		{"in Mail", nil, nil, mail, assert.True},
		{"in Other", nil, nil, otherMail, assert.False},
		{"in no Mail", nil, nil, noMail, assert.False},
		{"ex None filter mail", allMail, mail, nil, assert.False},
		{"ex Mail", mail, nil, allMail, assert.False},
		{"ex Other", otherMail, nil, allMail, assert.True},
		{"in and ex Mail", mail, nil, mail, assert.False},
		{"filter Mail", nil, mail, allMail, assert.True},
		{"filter Other", nil, otherMail, allMail, assert.False},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			pvs, err := cat.pathValues(repo, ent, Config{})
			require.NoError(t, err)

			result := passes(
				cat,
				pvs,
				entry,
				test.excludes,
				test.filters,
				test.includes)
			test.expect(t, result)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestContains() {
	target := "fnords"

	var (
		es                  = NewExchangeRestore(Any())
		noMail              = setScopesToDefault(es.Mails(None(), None()))
		does                = setScopesToDefault(es.Mails(Any(), []string{target}))
		doesNot             = setScopesToDefault(es.Mails(Any(), []string{"smarf"}))
		wrongType           = setScopesToDefault(es.Contacts(Any(), Any()))
		wrongTypeGoodTarget = setScopesToDefault(es.Contacts(Any(), Any()))
	)

	table := []struct {
		name   string
		scopes []ExchangeScope
		expect assert.BoolAssertionFunc
	}{
		{"no mail", noMail, assert.False},
		{"does contain", does, assert.True},
		{"does not contain", doesNot, assert.False},
		{"wrong type", wrongType, assert.False},
		{"wrong type but right target", wrongTypeGoodTarget, assert.False},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			var result bool
			for _, scope := range test.scopes {
				if scope.Matches(ExchangeMail, target) {
					result = true
					break
				}
			}
			test.expect(t, result)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestIsAny() {
	var (
		es           = NewExchangeRestore(Any())
		specificMail = setScopesToDefault(es.Mails(Any(), []string{"email"}))
		anyMail      = setScopesToDefault(es.Mails(Any(), Any()))
	)

	table := []struct {
		name   string
		scopes []ExchangeScope
		cat    exchangeCategory
		expect assert.BoolAssertionFunc
	}{
		{"specific mail", specificMail, ExchangeMail, assert.False},
		{"any mail", anyMail, ExchangeMail, assert.True},
		{"wrong category", anyMail, ExchangeEvent, assert.False},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			var result bool
			for _, scope := range test.scopes {
				if scope.IsAny(test.cat) {
					result = true
					break
				}
			}
			test.expect(t, result)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeCategory_leafCat() {
	table := []struct {
		cat    exchangeCategory
		expect exchangeCategory
	}{
		{exchangeCategory("foo"), exchangeCategory("foo")},
		{ExchangeCategoryUnknown, ExchangeCategoryUnknown},
		{ExchangeUser, ExchangeUser},
		{ExchangeMailFolder, ExchangeMail},
		{ExchangeMail, ExchangeMail},
		{ExchangeContactFolder, ExchangeContact},
		{ExchangeEvent, ExchangeEvent},
	}
	for _, test := range table {
		suite.Run(test.cat.String(), func() {
			assert.Equal(suite.T(), test.expect, test.cat.leafCat(), test.cat.String())
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathValues() {
	t := suite.T()

	var (
		contactPath = stubPath(t, "u", []string{"cfolder.d", "contactitem.d"}, path.ContactsCategory)
		contactLoc  = stubPath(t, "u", []string{"cfolder", "contactitem"}, path.ContactsCategory)
		contactMap  = map[categorizer][]string{
			ExchangeContactFolder: {contactLoc.Folder(false)},
			ExchangeContact:       {contactPath.Item(), "contact-short"},
		}
		contactOnlyNameMap = map[categorizer][]string{
			ExchangeContactFolder: {contactLoc.Folder(false)},
			ExchangeContact:       {"contact-short"},
		}
		eventPath = stubPath(t, "u", []string{"ecalendar.d", "eventitem.d"}, path.EventsCategory)
		eventLoc  = stubPath(t, "u", []string{"ecalendar", "eventitem"}, path.EventsCategory)
		eventMap  = map[categorizer][]string{
			ExchangeEventCalendar: {eventLoc.Folder(false)},
			ExchangeEvent:         {eventPath.Item(), "event-short"},
		}
		eventOnlyNameMap = map[categorizer][]string{
			ExchangeEventCalendar: {eventLoc.Folder(false)},
			ExchangeEvent:         {"event-short"},
		}
		mailPath = stubPath(t, "u", []string{"mfolder.d", "mailitem.d"}, path.EmailCategory)
		mailLoc  = stubPath(t, "u", []string{"mfolder", "mailitem"}, path.EmailCategory)
		mailMap  = map[categorizer][]string{
			ExchangeMailFolder: {mailLoc.Folder(false)},
			ExchangeMail:       {mailPath.Item(), "mail-short"},
		}
		mailOnlyNameMap = map[categorizer][]string{
			ExchangeMailFolder: {mailLoc.Folder(false)},
			ExchangeMail:       {"mail-short"},
		}
	)

	table := []struct {
		cat            exchangeCategory
		path           path.Path
		loc            path.Path
		short          string
		expect         map[categorizer][]string
		expectOnlyName map[categorizer][]string
	}{
		{ExchangeContact, contactPath, contactLoc, "contact-short", contactMap, contactOnlyNameMap},
		{ExchangeEvent, eventPath, eventLoc, "event-short", eventMap, eventOnlyNameMap},
		{ExchangeMail, mailPath, mailLoc, "mail-short", mailMap, mailOnlyNameMap},
	}
	for _, test := range table {
		suite.Run(string(test.cat), func() {
			t := suite.T()
			ent := details.Entry{
				RepoRef:     test.path.String(),
				ShortRef:    test.short,
				LocationRef: test.loc.Folder(true),
				ItemRef:     test.path.Item(),
			}

			pvs, err := test.cat.pathValues(test.path, ent, Config{})
			require.NoError(t, err)

			for k := range test.expect {
				assert.ElementsMatch(t, test.expect[k], pvs[k])
			}

			pvs, err = test.cat.pathValues(test.path, ent, Config{OnlyMatchItemNames: true})
			require.NoError(t, err)

			for k := range test.expectOnlyName {
				assert.ElementsMatch(t, test.expectOnlyName[k], pvs[k], k)
			}
		})
	}
}

func (suite *ExchangeSelectorSuite) TestExchangeCategory_PathKeys() {
	contact := []categorizer{ExchangeContactFolder, ExchangeContact}
	event := []categorizer{ExchangeEventCalendar, ExchangeEvent}
	mail := []categorizer{ExchangeMailFolder, ExchangeMail}
	user := []categorizer{ExchangeUser}

	var empty []categorizer

	table := []struct {
		cat    exchangeCategory
		expect []categorizer
	}{
		{ExchangeCategoryUnknown, empty},
		{ExchangeContact, contact},
		{ExchangeEvent, event},
		{ExchangeMail, mail},
		{ExchangeUser, user},
	}
	for _, test := range table {
		suite.Run(string(test.cat), func() {
			assert.Equal(suite.T(), test.cat.pathKeys(), test.expect)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestCategoryFromItemType() {
	table := []struct {
		name   string
		input  details.ItemType
		expect exchangeCategory
	}{
		{
			name:   "contact",
			input:  details.ExchangeContact,
			expect: ExchangeContact,
		},
		{
			name:   "event",
			input:  details.ExchangeEvent,
			expect: ExchangeEvent,
		},
		{
			name:   "mail",
			input:  details.ExchangeMail,
			expect: ExchangeMail,
		},
		{
			name:   "unknown",
			input:  details.UnknownType,
			expect: ExchangeCategoryUnknown,
		},
	}
	for _, test := range table {
		suite.Run(test.name, func() {
			t := suite.T()

			result := exchangeCategoryFromItemType(test.input)
			assert.Equal(t, test.expect, result)
		})
	}
}

func (suite *ExchangeSelectorSuite) TestCategory_PathType() {
	table := []struct {
		cat      exchangeCategory
		pathType path.CategoryType
	}{
		{ExchangeCategoryUnknown, path.UnknownCategory},
		{ExchangeContact, path.ContactsCategory},
		{ExchangeContactFolder, path.ContactsCategory},
		{ExchangeEvent, path.EventsCategory},
		{ExchangeEventCalendar, path.EventsCategory},
		{ExchangeMail, path.EmailCategory},
		{ExchangeMailFolder, path.EmailCategory},
		{ExchangeUser, path.UnknownCategory},
		{ExchangeInfoMailSender, path.EmailCategory},
		{ExchangeInfoMailSubject, path.EmailCategory},
		{ExchangeInfoMailReceivedAfter, path.EmailCategory},
		{ExchangeInfoMailReceivedBefore, path.EmailCategory},
		{ExchangeInfoContactName, path.ContactsCategory},
		{ExchangeInfoEventOrganizer, path.EventsCategory},
		{ExchangeInfoEventRecurs, path.EventsCategory},
		{ExchangeInfoEventStartsAfter, path.EventsCategory},
		{ExchangeInfoEventStartsBefore, path.EventsCategory},
		{ExchangeInfoEventSubject, path.EventsCategory},
	}
	for _, test := range table {
		suite.Run(test.cat.String(), func() {
			assert.Equal(suite.T(), test.pathType, test.cat.PathType())
		})
	}
}
