imapflow 1.6.1 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +14 -0
- package/lib/commands/list.js +41 -9
- package/lib/imap-flow.d.ts +3 -3
- package/lib/imap-flow.js +3 -3
- package/package.json +3 -3
- package/test/commands-integration-test.js +140 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.3](https://github.com/postalsys/imapflow/compare/v1.6.2...v1.6.3) (2026-07-28)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* assume folders are subscribed when the server reports no subscription state ([f576e3b](https://github.com/postalsys/imapflow/commit/f576e3b0eb34ee1340b34f66e389b8c29408d80a))
|
|
9
|
+
|
|
10
|
+
## [1.6.2](https://github.com/postalsys/imapflow/compare/v1.6.1...v1.6.2) (2026-07-28)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* never fall back to LSUB on an IMAP4rev2 session ([329f588](https://github.com/postalsys/imapflow/commit/329f58829592a3c9a9f69fa74387596a3e55eac3))
|
|
16
|
+
|
|
3
17
|
## [1.6.1](https://github.com/postalsys/imapflow/compare/v1.6.0...v1.6.1) (2026-07-27)
|
|
4
18
|
|
|
5
19
|
|
package/lib/commands/list.js
CHANGED
|
@@ -311,6 +311,14 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
311
311
|
// decision below
|
|
312
312
|
let successStage = null;
|
|
313
313
|
|
|
314
|
+
// Whether any source actually reported subscription state. RETURN (SUBSCRIBED)
|
|
315
|
+
// and LSUB are the only two, and a server can refuse both
|
|
316
|
+
let subscriptionStateKnown = false;
|
|
317
|
+
|
|
318
|
+
// A server may also volunteer \Subscribed on a plain LIST, which normalizeFlags
|
|
319
|
+
// folds into the entry - that counts as the state having been reported
|
|
320
|
+
let anyEntrySubscribed = () => entries.some(entry => entry.subscribed);
|
|
321
|
+
|
|
314
322
|
let lastRejectedStage = null;
|
|
315
323
|
let auxRetryInserted = false;
|
|
316
324
|
for (let i = 0; i < stages.length; i++) {
|
|
@@ -346,6 +354,7 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
346
354
|
}
|
|
347
355
|
}
|
|
348
356
|
successStage = stage;
|
|
357
|
+
subscriptionStateKnown = !!stage.subscribed;
|
|
349
358
|
break;
|
|
350
359
|
} catch (err) {
|
|
351
360
|
if (i === stages.length - 1 || !isRejectedCommand(err)) {
|
|
@@ -478,20 +487,28 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
478
487
|
response.next();
|
|
479
488
|
};
|
|
480
489
|
|
|
481
|
-
//
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
//
|
|
487
|
-
|
|
490
|
+
// Never sent on a rev2 session - LSUB is not part of that protocol version, and
|
|
491
|
+
// some servers break the rest of the session over the rejection, so this is
|
|
492
|
+
// decided up front rather than left to the skipLsub latch below. On rev1 it is
|
|
493
|
+
// skipped when RETURN (SUBSCRIBED) already answered. Safety net: if the extended
|
|
494
|
+
// LIST was accepted but not a single mailbox came back subscribed, assume the
|
|
495
|
+
// server silently ignored the option and fall back to LSUB anyway (an account
|
|
496
|
+
// with no subscriptions legitimately looks the same).
|
|
497
|
+
let needsLsub = !isRev2Active(connection) && (!successStage.subscribed || !anyEntrySubscribed());
|
|
498
|
+
if (needsLsub) {
|
|
499
|
+
// Reaching here means the listing did not settle the question after all -
|
|
500
|
+
// either no RETURN (SUBSCRIBED) was granted, or one was and the server
|
|
501
|
+
// ignored it. Only LSUB can answer now
|
|
502
|
+
subscriptionStateKnown = false;
|
|
503
|
+
}
|
|
488
504
|
if (needsLsub && !connection.skipLsub) {
|
|
489
505
|
try {
|
|
490
506
|
await runLsub();
|
|
507
|
+
subscriptionStateKnown = true;
|
|
491
508
|
} catch (err) {
|
|
492
509
|
if (isRejectedCommand(err)) {
|
|
493
|
-
// Tagged BAD: the server does not
|
|
494
|
-
//
|
|
510
|
+
// Tagged BAD: the server does not implement LSUB despite advertising
|
|
511
|
+
// rev1 - skip it for the rest of this connection
|
|
495
512
|
connection.skipLsub = true;
|
|
496
513
|
} else if (err.responseStatus !== 'NO' || err.code === 'ETHROTTLE') {
|
|
497
514
|
// Transport failures and throttling: rethrow, every follow-up
|
|
@@ -524,6 +541,21 @@ module.exports = async (connection, reference, mailbox, options) => {
|
|
|
524
541
|
}
|
|
525
542
|
}
|
|
526
543
|
|
|
544
|
+
// No source answered, so "not subscribed" was never actually reported for any of
|
|
545
|
+
// these folders - the state is unknown, not false. Reporting the whole listing as
|
|
546
|
+
// unsubscribed would hide every folder from a client that filters on subscription
|
|
547
|
+
// state, so assume subscribed instead. Phantom entries are excluded, the same way
|
|
548
|
+
// the rest of this file declines to trust them. NB! a transient LSUB NO lands here
|
|
549
|
+
// too, so the assumption can hold for one listing and be replaced by real state on
|
|
550
|
+
// the next
|
|
551
|
+
if (!subscriptionStateKnown && !anyEntrySubscribed()) {
|
|
552
|
+
for (let entry of entries) {
|
|
553
|
+
if (!entry.flags.has('\\NonExistent')) {
|
|
554
|
+
entry.subscribed = true;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
527
559
|
// INBOX should always appear as subscribed regardless of LSUB results
|
|
528
560
|
let inboxEntry = entries.find(entry => entry.specialUse === '\\Inbox');
|
|
529
561
|
if (inboxEntry && !inboxEntry.subscribed) {
|
package/lib/imap-flow.d.ts
CHANGED
|
@@ -129,7 +129,7 @@ export interface MailboxObject {
|
|
|
129
129
|
specialUse?: string;
|
|
130
130
|
/** True if mailbox was found from the output of LIST command */
|
|
131
131
|
listed?: boolean;
|
|
132
|
-
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers */
|
|
132
|
+
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed */
|
|
133
133
|
subscribed?: boolean;
|
|
134
134
|
/** A Set of flags available to use in this mailbox. If it is not set or includes special flag "\*" then any flag can be used */
|
|
135
135
|
permanentFlags?: Set<string>;
|
|
@@ -212,7 +212,7 @@ export interface ListResponse {
|
|
|
212
212
|
specialUseSource?: 'user' | 'extension' | 'name';
|
|
213
213
|
/** True if mailbox was found from the output of LIST command */
|
|
214
214
|
listed: boolean;
|
|
215
|
-
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers */
|
|
215
|
+
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed */
|
|
216
216
|
subscribed: boolean;
|
|
217
217
|
/** If statusQuery was used, then this value includes the status response */
|
|
218
218
|
status?: StatusObject;
|
|
@@ -268,7 +268,7 @@ export interface ListTreeResponse {
|
|
|
268
268
|
specialUse?: string;
|
|
269
269
|
/** True if mailbox was found from the output of LIST command */
|
|
270
270
|
listed?: boolean;
|
|
271
|
-
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers */
|
|
271
|
+
/** True if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed */
|
|
272
272
|
subscribed?: boolean;
|
|
273
273
|
/** If true then this mailbox can not be selected in the UI */
|
|
274
274
|
disabled?: boolean;
|
package/lib/imap-flow.js
CHANGED
|
@@ -71,7 +71,7 @@ const states = {
|
|
|
71
71
|
* @property {Set<string>} flags list of flags for this mailbox
|
|
72
72
|
* @property {String} [specialUse] one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
73
73
|
* @property {Boolean} listed `true` if mailbox was found from the output of LIST command
|
|
74
|
-
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers
|
|
74
|
+
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed
|
|
75
75
|
* @property {Set<string>} permanentFlags A Set of flags available to use in this mailbox. If it is not set or includes special flag "\\\*" then any flag can be used.
|
|
76
76
|
* @property {String} [mailboxId] unique mailbox ID if server has `OBJECTID` extension enabled
|
|
77
77
|
* @property {BigInt} [highestModseq] latest known modseq value if server has CONDSTORE or XYMHIGHESTMODSEQ enabled
|
|
@@ -2518,7 +2518,7 @@ class ImapFlow extends EventEmitter {
|
|
|
2518
2518
|
* @property {String} specialUse one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
2519
2519
|
* @property {String} [specialUseSource] how `specialUse` was determined: `"user"` (from `specialUseHints`), `"extension"` (SPECIAL-USE or XLIST flag reported by the server) or `"name"` (matched against known localized folder names)
|
|
2520
2520
|
* @property {Boolean} listed `true` if mailbox was found from the output of LIST command
|
|
2521
|
-
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers
|
|
2521
|
+
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed
|
|
2522
2522
|
* @property {StatusObject} [status] If `statusQuery` was used, then this value includes the status response
|
|
2523
2523
|
*/
|
|
2524
2524
|
|
|
@@ -2569,7 +2569,7 @@ class ImapFlow extends EventEmitter {
|
|
|
2569
2569
|
* @property {Set<string>} flags list of flags for this mailbox
|
|
2570
2570
|
* @property {String} specialUse one of special-use flags (if applicable): "\All", "\Archive", "\Drafts", "\Flagged", "\Junk", "\Sent", "\Trash". Additionally INBOX has non-standard "\Inbox" flag set
|
|
2571
2571
|
* @property {Boolean} listed `true` if mailbox was found from the output of LIST command
|
|
2572
|
-
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers
|
|
2572
|
+
* @property {Boolean} subscribed `true` if the mailbox is subscribed - reported by LSUB or by LIST RETURN (SUBSCRIBED) on LIST-EXTENDED/IMAP4rev2 servers. Servers that answer neither report no subscription state at all, and every mailbox is then assumed to be subscribed
|
|
2573
2573
|
* @property {Boolean} disabled If `true` then this mailbox can not be selected in the UI
|
|
2574
2574
|
* @property {ListTreeResponse[]} folders An array of subfolders
|
|
2575
2575
|
* @property {StatusObject} [status] If `statusQuery` was used, then this value includes the status response
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "imapflow",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.3",
|
|
4
4
|
"description": "IMAP Client for Node",
|
|
5
5
|
"main": "lib/imap-flow.js",
|
|
6
6
|
"types": "lib/imap-flow.d.ts",
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
"homepage": "https://imapflow.com/",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@eslint/js": "10.0.1",
|
|
33
|
-
"@types/node": "26.1.
|
|
33
|
+
"@types/node": "26.1.2",
|
|
34
34
|
"c8": "12.0.0",
|
|
35
|
-
"eslint": "10.
|
|
35
|
+
"eslint": "10.8.0",
|
|
36
36
|
"eslint-config-nodemailer": "1.2.0",
|
|
37
37
|
"eslint-config-prettier": "10.1.8",
|
|
38
38
|
"grunt": "1.6.2",
|
|
@@ -4168,7 +4168,7 @@ module.exports['Commands: list survives LSUB rejection'] = async test => {
|
|
|
4168
4168
|
exec: async (cmd, attrs, opts) => {
|
|
4169
4169
|
if (cmd === 'LSUB') {
|
|
4170
4170
|
lsubCalls++;
|
|
4171
|
-
// e.g. Exchange
|
|
4171
|
+
// e.g. Exchange responds "BAD Command Argument Error"
|
|
4172
4172
|
throw commandError('Command failed', 'BAD');
|
|
4173
4173
|
}
|
|
4174
4174
|
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
@@ -4189,6 +4189,9 @@ module.exports['Commands: list survives LSUB rejection'] = async test => {
|
|
|
4189
4189
|
test.ok(inbox);
|
|
4190
4190
|
// INBOX is always reported as subscribed even without LSUB data
|
|
4191
4191
|
test.equal(inbox.subscribed, true);
|
|
4192
|
+
// Nothing reported subscription state, so it is unknown rather than false - every
|
|
4193
|
+
// folder is reported as subscribed instead of none of them
|
|
4194
|
+
test.equal(result.find(e => e.path === 'Folder1').subscribed, true);
|
|
4192
4195
|
|
|
4193
4196
|
// The rejection is remembered - a follow-up listing skips LSUB entirely
|
|
4194
4197
|
test.equal(connection.skipLsub, true);
|
|
@@ -4197,6 +4200,107 @@ module.exports['Commands: list survives LSUB rejection'] = async test => {
|
|
|
4197
4200
|
test.done();
|
|
4198
4201
|
};
|
|
4199
4202
|
|
|
4203
|
+
module.exports['Commands: list keeps a genuinely empty subscription set'] = async test => {
|
|
4204
|
+
// LSUB answers, it just has nothing to report. That is a real "nothing is
|
|
4205
|
+
// subscribed", not the unknown state, so it must not be overwritten
|
|
4206
|
+
const connection = createMockConnection({
|
|
4207
|
+
state: 3,
|
|
4208
|
+
exec: async (cmd, attrs, opts) => {
|
|
4209
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
4210
|
+
await opts.untagged.LIST({
|
|
4211
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
4212
|
+
});
|
|
4213
|
+
}
|
|
4214
|
+
return { next: () => {} };
|
|
4215
|
+
}
|
|
4216
|
+
});
|
|
4217
|
+
|
|
4218
|
+
const result = await listCommand(connection, '', '*');
|
|
4219
|
+
test.ok(!result.find(entry => entry.path === 'Folder1').subscribed);
|
|
4220
|
+
test.done();
|
|
4221
|
+
};
|
|
4222
|
+
|
|
4223
|
+
module.exports['Commands: list treats an ignored RETURN (SUBSCRIBED) plus a rejected LSUB as unknown'] = async test => {
|
|
4224
|
+
// The server accepts the RETURN option but reports no \Subscribed at all, which is
|
|
4225
|
+
// why the listing falls back to LSUB - and that is rejected too. Accepting the
|
|
4226
|
+
// command is not the same as answering it, so this is the unknown state
|
|
4227
|
+
const connection = createMockConnection({
|
|
4228
|
+
state: 3,
|
|
4229
|
+
capabilities: new Map([
|
|
4230
|
+
['IMAP4rev1', true],
|
|
4231
|
+
['LIST-EXTENDED', true]
|
|
4232
|
+
]),
|
|
4233
|
+
exec: async (cmd, attrs, opts) => {
|
|
4234
|
+
if (cmd === 'LSUB') {
|
|
4235
|
+
throw commandError('Command failed', 'BAD');
|
|
4236
|
+
}
|
|
4237
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
4238
|
+
await opts.untagged.LIST({
|
|
4239
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
4240
|
+
});
|
|
4241
|
+
}
|
|
4242
|
+
return { next: () => {} };
|
|
4243
|
+
}
|
|
4244
|
+
});
|
|
4245
|
+
|
|
4246
|
+
const result = await listCommand(connection, '', '*');
|
|
4247
|
+
test.equal(result.find(entry => entry.path === 'Folder1').subscribed, true);
|
|
4248
|
+
test.done();
|
|
4249
|
+
};
|
|
4250
|
+
|
|
4251
|
+
module.exports['Commands: list leaves phantom folders out of the assumed subscription'] = async test => {
|
|
4252
|
+
const connection = createMockConnection({
|
|
4253
|
+
state: 3,
|
|
4254
|
+
capabilities: new Map([['IMAP4rev2', true]]),
|
|
4255
|
+
enabled: new Set(['IMAP4REV2']),
|
|
4256
|
+
skipListSubscribedArg: true,
|
|
4257
|
+
exec: async (cmd, attrs, opts) => {
|
|
4258
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
4259
|
+
await opts.untagged.LIST({
|
|
4260
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
4261
|
+
});
|
|
4262
|
+
await opts.untagged.LIST({
|
|
4263
|
+
attributes: [[{ value: '\\NonExistent' }], { value: '/' }, { value: 'Ghost' }]
|
|
4264
|
+
});
|
|
4265
|
+
}
|
|
4266
|
+
return { next: () => {} };
|
|
4267
|
+
}
|
|
4268
|
+
});
|
|
4269
|
+
|
|
4270
|
+
const result = await listCommand(connection, '', '*');
|
|
4271
|
+
test.equal(result.find(entry => entry.path === 'Folder1').subscribed, true);
|
|
4272
|
+
// A folder the server says does not exist is not claimed to be subscribed
|
|
4273
|
+
test.ok(!result.find(entry => entry.path === 'Ghost').subscribed);
|
|
4274
|
+
test.done();
|
|
4275
|
+
};
|
|
4276
|
+
|
|
4277
|
+
module.exports['Commands: list keeps subscription flags volunteered by a plain LIST'] = async test => {
|
|
4278
|
+
// No RETURN option was granted and LSUB is not available on rev2, but the server
|
|
4279
|
+
// reported \Subscribed on its own - that is real state and must not be widened
|
|
4280
|
+
const connection = createMockConnection({
|
|
4281
|
+
state: 3,
|
|
4282
|
+
capabilities: new Map([['IMAP4rev2', true]]),
|
|
4283
|
+
enabled: new Set(['IMAP4REV2']),
|
|
4284
|
+
skipListSubscribedArg: true,
|
|
4285
|
+
exec: async (cmd, attrs, opts) => {
|
|
4286
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
4287
|
+
await opts.untagged.LIST({
|
|
4288
|
+
attributes: [[{ value: '\\Subscribed' }, { value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
4289
|
+
});
|
|
4290
|
+
await opts.untagged.LIST({
|
|
4291
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder2' }]
|
|
4292
|
+
});
|
|
4293
|
+
}
|
|
4294
|
+
return { next: () => {} };
|
|
4295
|
+
}
|
|
4296
|
+
});
|
|
4297
|
+
|
|
4298
|
+
const result = await listCommand(connection, '', '*');
|
|
4299
|
+
test.equal(result.find(entry => entry.path === 'Folder1').subscribed, true);
|
|
4300
|
+
test.ok(!result.find(entry => entry.path === 'Folder2').subscribed);
|
|
4301
|
+
test.done();
|
|
4302
|
+
};
|
|
4303
|
+
|
|
4200
4304
|
module.exports['Commands: list fails when LSUB dies without a server rejection'] = async test => {
|
|
4201
4305
|
const connection = createMockConnection({
|
|
4202
4306
|
state: 3,
|
|
@@ -4304,6 +4408,41 @@ module.exports['Commands: list retries with plain LIST when RETURN is rejected']
|
|
|
4304
4408
|
test.done();
|
|
4305
4409
|
};
|
|
4306
4410
|
|
|
4411
|
+
module.exports['Commands: list never falls back to LSUB on a rev2 session'] = async test => {
|
|
4412
|
+
let lsubCalled = false;
|
|
4413
|
+
// An Exchange-alike that advertises both revisions and negotiates rev2 via ENABLE,
|
|
4414
|
+
// having already proved it rejects RETURN (SUBSCRIBED). The listing therefore
|
|
4415
|
+
// carries no subscription state - and the LSUB that rev1 would fall back to is not
|
|
4416
|
+
// part of rev2, so asking anyway only risks upsetting the session.
|
|
4417
|
+
const connection = createMockConnection({
|
|
4418
|
+
state: 3,
|
|
4419
|
+
capabilities: new Map([
|
|
4420
|
+
['IMAP4rev2', true],
|
|
4421
|
+
['IMAP4rev1', true]
|
|
4422
|
+
]),
|
|
4423
|
+
enabled: new Set(['IMAP4REV2']),
|
|
4424
|
+
skipListSubscribedArg: true,
|
|
4425
|
+
exec: async (cmd, attrs, opts) => {
|
|
4426
|
+
if (cmd === 'LSUB') {
|
|
4427
|
+
lsubCalled = true;
|
|
4428
|
+
}
|
|
4429
|
+
if (cmd === 'LIST' && opts && opts.untagged && opts.untagged.LIST) {
|
|
4430
|
+
await opts.untagged.LIST({
|
|
4431
|
+
attributes: [[{ value: '\\HasNoChildren' }], { value: '/' }, { value: 'Folder1' }]
|
|
4432
|
+
});
|
|
4433
|
+
}
|
|
4434
|
+
return { next: () => {} };
|
|
4435
|
+
}
|
|
4436
|
+
});
|
|
4437
|
+
|
|
4438
|
+
const result = await listCommand(connection, '', '*');
|
|
4439
|
+
test.equal(lsubCalled, false);
|
|
4440
|
+
// Neither source could answer, so the folder is reported as subscribed rather than
|
|
4441
|
+
// claiming the server said it is not
|
|
4442
|
+
test.equal(result.find(entry => entry.path === 'Folder1').subscribed, true);
|
|
4443
|
+
test.done();
|
|
4444
|
+
};
|
|
4445
|
+
|
|
4307
4446
|
module.exports['Commands: list does not retry extended LIST on transport errors'] = async test => {
|
|
4308
4447
|
let listCalls = 0;
|
|
4309
4448
|
const connection = createMockConnection({
|