@@ -215,6 +215,25 @@ describe('dav helpers', () => {
215215 expect ( result ) . toBe ( false )
216216 } )
217217
218+ it ( 'lowercases the email and requests a case-insensitive collation' , async ( ) => {
219+ // The stored vCard EMAIL may be mixed-case and admin paths hand through raw
220+ // input, so the filter must normalize the query and pin the collation rather
221+ // than relying on the DAV server default (see findUserByEmail).
222+ vi . mocked ( addressBookQuery ) . mockResolvedValue ( [ ] )
223+ const account = createCardDAVAccount ( config )
224+ await findUserByEmail ( account , 'Max@Example.COM' )
225+ const callArgs = vi . mocked ( addressBookQuery ) . mock . calls [ 0 ] ! [ 0 ] as Record < string , unknown >
226+ const filters = callArgs . filters as {
227+ 'prop-filter' : {
228+ _attributes : { name : string }
229+ 'text-match' : { _attributes : { collation : string } ; _text : string }
230+ }
231+ }
232+ expect ( filters [ 'prop-filter' ] . _attributes . name ) . toBe ( 'EMAIL' )
233+ expect ( filters [ 'prop-filter' ] [ 'text-match' ] . _text ) . toBe ( 'max@example.com' )
234+ expect ( filters [ 'prop-filter' ] [ 'text-match' ] . _attributes . collation ) . toBe ( 'i;unicode-casemap' )
235+ } )
236+
218237 it ( 'returns false when result has missing or empty addressData' , async ( ) => {
219238 // Hits the defensive `if (typeof data !== 'string' || data.length === 0)`
220239 // branch — Baikal can return a single hit but with no `address-data`
@@ -273,6 +292,21 @@ describe('dav helpers', () => {
273292 const result = await findUserByToken ( account , 'bad-token' )
274293 expect ( result ) . toBe ( false )
275294 } )
295+
296+ it ( 'matches the token exactly without a collation' , async ( ) => {
297+ // Login tokens are high-entropy and case-sensitive: the filter must NOT relax
298+ // matching with a collation the way the EMAIL lookup does.
299+ vi . mocked ( addressBookQuery ) . mockResolvedValue ( [ ] )
300+ const account = createCardDAVAccount ( config )
301+ await findUserByToken ( account , 'AbC-ToKeN' )
302+ const callArgs = vi . mocked ( addressBookQuery ) . mock . calls [ 0 ] ! [ 0 ] as Record < string , unknown >
303+ const filters = callArgs . filters as {
304+ 'prop-filter' : { _attributes : { name : string } ; 'text-match' : unknown }
305+ }
306+ expect ( filters [ 'prop-filter' ] . _attributes . name ) . toBe ( X_LOGIN_TOKEN )
307+ // Plain string payload => no `collation` attribute, value passed verbatim.
308+ expect ( filters [ 'prop-filter' ] [ 'text-match' ] ) . toBe ( 'AbC-ToKeN' )
309+ } )
276310 } )
277311
278312 describe ( 'saveUser' , ( ) => {
0 commit comments