imapflow 1.7.7 → 2.0.0
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/CHANGELOG.md +27 -0
- package/README.md +8 -2
- package/dist/cjs/charsets.d.ts +1 -0
- package/dist/cjs/charsets.js +294 -0
- package/dist/cjs/commands/append.d.ts +22 -0
- package/dist/cjs/commands/append.js +151 -0
- package/dist/cjs/commands/authenticate.d.ts +24 -0
- package/dist/cjs/commands/authenticate.js +223 -0
- package/dist/cjs/commands/capability.d.ts +8 -0
- package/dist/cjs/commands/capability.js +32 -0
- package/dist/cjs/commands/close.d.ts +8 -0
- package/dist/cjs/commands/close.js +39 -0
- package/dist/cjs/commands/compress.d.ts +8 -0
- package/dist/cjs/commands/compress.js +56 -0
- package/dist/cjs/commands/copy.d.ts +13 -0
- package/dist/cjs/commands/copy.js +44 -0
- package/dist/cjs/commands/copyuid-parser.d.ts +11 -0
- package/dist/cjs/commands/copyuid-parser.js +32 -0
- package/dist/cjs/commands/create.d.ts +11 -0
- package/dist/cjs/commands/create.js +80 -0
- package/dist/cjs/commands/delete.d.ts +11 -0
- package/dist/cjs/commands/delete.js +40 -0
- package/dist/cjs/commands/enable.d.ts +9 -0
- package/dist/cjs/commands/enable.js +61 -0
- package/dist/cjs/commands/esearch-parser.d.ts +17 -0
- package/dist/cjs/commands/esearch-parser.js +91 -0
- package/dist/cjs/commands/expunge.d.ts +12 -0
- package/dist/cjs/commands/expunge.js +60 -0
- package/dist/cjs/commands/fetch.d.ts +30 -0
- package/dist/cjs/commands/fetch.js +241 -0
- package/dist/cjs/commands/id.d.ts +10 -0
- package/dist/cjs/commands/id.js +80 -0
- package/dist/cjs/commands/idle.d.ts +9 -0
- package/dist/cjs/commands/idle.js +347 -0
- package/dist/cjs/commands/list.d.ts +16 -0
- package/dist/cjs/commands/list.js +518 -0
- package/dist/cjs/commands/login.d.ts +11 -0
- package/dist/cjs/commands/login.js +42 -0
- package/dist/cjs/commands/logout.d.ts +8 -0
- package/dist/cjs/commands/logout.js +47 -0
- package/dist/cjs/commands/move.d.ts +13 -0
- package/dist/cjs/commands/move.js +57 -0
- package/dist/cjs/commands/namespace.d.ts +25 -0
- package/dist/cjs/commands/namespace.js +139 -0
- package/dist/cjs/commands/noop.d.ts +8 -0
- package/dist/cjs/commands/noop.js +22 -0
- package/dist/cjs/commands/quota.d.ts +10 -0
- package/dist/cjs/commands/quota.js +119 -0
- package/dist/cjs/commands/rename.d.ts +12 -0
- package/dist/cjs/commands/rename.js +48 -0
- package/dist/cjs/commands/search.d.ts +15 -0
- package/dist/cjs/commands/search.js +228 -0
- package/dist/cjs/commands/select.d.ts +25 -0
- package/dist/cjs/commands/select.js +250 -0
- package/dist/cjs/commands/starttls.d.ts +8 -0
- package/dist/cjs/commands/starttls.js +30 -0
- package/dist/cjs/commands/status-fields.d.ts +14 -0
- package/dist/cjs/commands/status-fields.js +61 -0
- package/dist/cjs/commands/status.d.ts +12 -0
- package/dist/cjs/commands/status.js +108 -0
- package/dist/cjs/commands/store.d.ts +19 -0
- package/dist/cjs/commands/store.js +93 -0
- package/dist/cjs/commands/subscribe.d.ts +9 -0
- package/dist/cjs/commands/subscribe.js +31 -0
- package/dist/cjs/commands/unsubscribe.d.ts +9 -0
- package/dist/cjs/commands/unsubscribe.js +31 -0
- package/dist/cjs/connection-deadline.d.ts +49 -0
- package/dist/cjs/connection-deadline.js +91 -0
- package/dist/cjs/errors.d.ts +83 -0
- package/dist/cjs/errors.js +13 -0
- package/dist/cjs/handler/imap-compiler.d.ts +24 -0
- package/dist/cjs/handler/imap-compiler.js +285 -0
- package/dist/cjs/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/cjs/handler/imap-formal-syntax.js +121 -0
- package/dist/cjs/handler/imap-handler.d.ts +9 -0
- package/dist/cjs/handler/imap-handler.js +10 -0
- package/dist/cjs/handler/imap-parser.d.ts +16 -0
- package/dist/cjs/handler/imap-parser.js +90 -0
- package/dist/cjs/handler/imap-stream.d.ts +181 -0
- package/dist/cjs/handler/imap-stream.js +446 -0
- package/dist/cjs/handler/limits.d.ts +25 -0
- package/dist/cjs/handler/limits.js +51 -0
- package/dist/cjs/handler/parser-instance.d.ts +68 -0
- package/dist/cjs/handler/parser-instance.js +223 -0
- package/dist/cjs/handler/token-parser.d.ts +91 -0
- package/dist/cjs/handler/token-parser.js +673 -0
- package/dist/cjs/handler/types.d.ts +91 -0
- package/dist/cjs/handler/types.js +4 -0
- package/dist/cjs/imap-commands.d.ts +16 -0
- package/dist/cjs/imap-commands.js +74 -0
- package/dist/cjs/imap-flow.d.ts +676 -0
- package/dist/cjs/imap-flow.js +3949 -0
- package/dist/cjs/jp-decoder.d.ts +12 -0
- package/dist/cjs/jp-decoder.js +79 -0
- package/dist/cjs/limited-passthrough.d.ts +25 -0
- package/dist/cjs/limited-passthrough.js +54 -0
- package/dist/cjs/logger.d.ts +3 -0
- package/dist/cjs/logger.js +11 -0
- package/dist/cjs/package-info.d.ts +3 -0
- package/dist/cjs/package-info.js +7 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/proxy-connection.d.ts +33 -0
- package/dist/cjs/proxy-connection.js +392 -0
- package/dist/cjs/search-compiler.d.ts +34 -0
- package/dist/cjs/search-compiler.js +476 -0
- package/dist/cjs/special-use.d.ts +22 -0
- package/dist/cjs/special-use.js +911 -0
- package/dist/cjs/tools.d.ts +427 -0
- package/dist/cjs/tools.js +1496 -0
- package/{lib/imap-flow.d.ts → dist/cjs/types.d.ts} +386 -516
- package/dist/cjs/types.js +5 -0
- package/dist/esm/charsets.d.ts +1 -0
- package/{lib → dist/esm}/charsets.js +1 -6
- package/dist/esm/commands/append.d.ts +22 -0
- package/{lib → dist/esm}/commands/append.js +22 -52
- package/dist/esm/commands/authenticate.d.ts +24 -0
- package/{lib → dist/esm}/commands/authenticate.js +62 -87
- package/dist/esm/commands/capability.d.ts +8 -0
- package/{lib → dist/esm}/commands/capability.js +6 -9
- package/dist/esm/commands/close.d.ts +8 -0
- package/{lib → dist/esm}/commands/close.js +6 -10
- package/dist/esm/commands/compress.d.ts +8 -0
- package/{lib → dist/esm}/commands/compress.js +7 -11
- package/dist/esm/commands/copy.d.ts +13 -0
- package/{lib → dist/esm}/commands/copy.js +12 -20
- package/dist/esm/commands/copyuid-parser.d.ts +11 -0
- package/{lib → dist/esm}/commands/copyuid-parser.js +9 -15
- package/dist/esm/commands/create.d.ts +11 -0
- package/{lib → dist/esm}/commands/create.js +13 -27
- package/dist/esm/commands/delete.d.ts +11 -0
- package/{lib → dist/esm}/commands/delete.js +9 -14
- package/dist/esm/commands/enable.d.ts +9 -0
- package/{lib → dist/esm}/commands/enable.js +23 -30
- package/dist/esm/commands/esearch-parser.d.ts +17 -0
- package/dist/esm/commands/esearch-parser.js +88 -0
- package/dist/esm/commands/expunge.d.ts +12 -0
- package/{lib → dist/esm}/commands/expunge.js +17 -22
- package/dist/esm/commands/fetch.d.ts +30 -0
- package/{lib → dist/esm}/commands/fetch.js +32 -64
- package/dist/esm/commands/id.d.ts +10 -0
- package/{lib → dist/esm}/commands/id.js +17 -23
- package/dist/esm/commands/idle.d.ts +9 -0
- package/{lib → dist/esm}/commands/idle.js +47 -81
- package/dist/esm/commands/list.d.ts +16 -0
- package/{lib → dist/esm}/commands/list.js +56 -121
- package/dist/esm/commands/login.d.ts +11 -0
- package/{lib → dist/esm}/commands/login.js +10 -15
- package/dist/esm/commands/logout.d.ts +8 -0
- package/{lib → dist/esm}/commands/logout.js +9 -11
- package/dist/esm/commands/move.d.ts +13 -0
- package/{lib → dist/esm}/commands/move.js +13 -21
- package/dist/esm/commands/namespace.d.ts +25 -0
- package/{lib → dist/esm}/commands/namespace.js +34 -44
- package/dist/esm/commands/noop.d.ts +8 -0
- package/{lib → dist/esm}/commands/noop.js +6 -7
- package/dist/esm/commands/quota.d.ts +10 -0
- package/{lib → dist/esm}/commands/quota.js +18 -36
- package/dist/esm/commands/rename.d.ts +12 -0
- package/{lib → dist/esm}/commands/rename.js +10 -15
- package/dist/esm/commands/search.d.ts +15 -0
- package/{lib → dist/esm}/commands/search.js +36 -135
- package/dist/esm/commands/select.d.ts +25 -0
- package/{lib → dist/esm}/commands/select.js +33 -64
- package/dist/esm/commands/starttls.d.ts +8 -0
- package/{lib → dist/esm}/commands/starttls.js +6 -8
- package/dist/esm/commands/status-fields.d.ts +14 -0
- package/{lib → dist/esm}/commands/status-fields.js +5 -16
- package/dist/esm/commands/status.d.ts +12 -0
- package/{lib → dist/esm}/commands/status.js +18 -29
- package/dist/esm/commands/store.d.ts +19 -0
- package/{lib → dist/esm}/commands/store.js +24 -37
- package/dist/esm/commands/subscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/subscribe.js +8 -12
- package/dist/esm/commands/unsubscribe.d.ts +9 -0
- package/{lib → dist/esm}/commands/unsubscribe.js +8 -12
- package/dist/esm/connection-deadline.d.ts +49 -0
- package/{lib → dist/esm}/connection-deadline.js +14 -25
- package/dist/esm/errors.d.ts +83 -0
- package/dist/esm/errors.js +9 -0
- package/dist/esm/handler/imap-compiler.d.ts +24 -0
- package/{lib → dist/esm}/handler/imap-compiler.js +22 -80
- package/dist/esm/handler/imap-formal-syntax.d.ts +28 -0
- package/dist/esm/handler/imap-formal-syntax.js +117 -0
- package/dist/esm/handler/imap-handler.d.ts +9 -0
- package/dist/esm/handler/imap-handler.js +9 -0
- package/dist/esm/handler/imap-parser.d.ts +16 -0
- package/{lib → dist/esm}/handler/imap-parser.js +31 -44
- package/dist/esm/handler/imap-stream.d.ts +181 -0
- package/{lib → dist/esm}/handler/imap-stream.js +29 -121
- package/dist/esm/handler/limits.d.ts +25 -0
- package/{lib → dist/esm}/handler/limits.js +13 -22
- package/dist/esm/handler/parser-instance.d.ts +68 -0
- package/{lib → dist/esm}/handler/parser-instance.js +19 -47
- package/dist/esm/handler/token-parser.d.ts +91 -0
- package/{lib → dist/esm}/handler/token-parser.js +71 -155
- package/dist/esm/handler/types.d.ts +91 -0
- package/dist/esm/handler/types.js +3 -0
- package/dist/esm/imap-commands.d.ts +16 -0
- package/dist/esm/imap-commands.js +67 -0
- package/dist/esm/imap-flow.d.ts +676 -0
- package/{lib → dist/esm}/imap-flow.js +785 -1802
- package/dist/esm/jp-decoder.d.ts +12 -0
- package/{lib → dist/esm}/jp-decoder.js +6 -21
- package/dist/esm/limited-passthrough.d.ts +25 -0
- package/{lib → dist/esm}/limited-passthrough.js +7 -20
- package/dist/esm/logger.d.ts +3 -0
- package/dist/esm/logger.js +4 -0
- package/dist/esm/package-info.d.ts +3 -0
- package/dist/esm/package-info.js +4 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/proxy-connection.d.ts +33 -0
- package/{lib → dist/esm}/proxy-connection.js +56 -127
- package/dist/esm/search-compiler.d.ts +34 -0
- package/{lib → dist/esm}/search-compiler.js +54 -110
- package/dist/esm/special-use.d.ts +22 -0
- package/dist/esm/special-use.js +907 -0
- package/dist/esm/tools.d.ts +427 -0
- package/dist/esm/tools.js +1446 -0
- package/dist/esm/types.d.ts +828 -0
- package/dist/esm/types.js +4 -0
- package/package.json +60 -20
- package/.gitattributes +0 -1
- package/.github/CODE_OF_CONDUCT.md +0 -76
- package/.github/FUNDING.yml +0 -4
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -40
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/contributing.md +0 -17
- package/.github/workflows/release.yaml +0 -36
- package/.github/workflows/stale.yml +0 -29
- package/.github/workflows/test.yml +0 -51
- package/.ncurc.js +0 -4
- package/.prettierignore +0 -4
- package/.prettierrc.js +0 -8
- package/.release-please-manifest.json +0 -3
- package/CLAUDE.md +0 -104
- package/Gruntfile.js +0 -23
- package/eslint.config.js +0 -45
- package/lib/handler/imap-formal-syntax.js +0 -189
- package/lib/handler/imap-handler.js +0 -17
- package/lib/imap-commands.js +0 -45
- package/lib/logger.js +0 -5
- package/lib/special-use.js +0 -923
- package/lib/tools.js +0 -1612
- package/release-please-config.json +0 -10
- package/test/authentication-test.js +0 -101
- package/test/auto-idle-test.js +0 -470
- package/test/bodystructure-test.js +0 -899
- package/test/charsets-test.js +0 -161
- package/test/commands-branches-test.js +0 -1095
- package/test/commands-integration-test.js +0 -11124
- package/test/commands-test.js +0 -73
- package/test/connection-edge-cases-test.js +0 -1828
- package/test/connection-test.js +0 -162
- package/test/copyuid-parser-test.js +0 -173
- package/test/fetch-generator-test.js +0 -218
- package/test/fixtures/fake-timers.js +0 -115
- package/test/fixtures/serialized-mimetorture.js +0 -2738
- package/test/fixtures/test-client.js +0 -57
- package/test/fixtures/test-tls.js +0 -8
- package/test/handler-branches-test.js +0 -310
- package/test/idle-polling-test.js +0 -518
- package/test/imap-compiler-test.js +0 -809
- package/test/imap-flow-compress-test.js +0 -166
- package/test/imap-flow-coverage-test.js +0 -612
- package/test/imap-flow-fetch-download-test.js +0 -873
- package/test/imap-flow-internals-test.js +0 -725
- package/test/imap-flow-methods-test.js +0 -889
- package/test/imap-flow-proxy-paths-test.js +0 -366
- package/test/imap-flow-secure-test.js +0 -573
- package/test/imap-flow-server-test.js +0 -1474
- package/test/imap-formal-syntax-test.js +0 -293
- package/test/imap-parser-test.js +0 -1474
- package/test/imap-stream-edge-cases-test.js +0 -666
- package/test/imap-stream-test.js +0 -177
- package/test/imapflow-test.js +0 -258
- package/test/integration/README.md +0 -52
- package/test/integration/dovecot-test.conf +0 -27
- package/test/integration/rev2-live-test.js +0 -431
- package/test/integration/run-rev2-tests.sh +0 -75
- package/test/integration-test.js +0 -83
- package/test/jp-decoder-test.js +0 -304
- package/test/limited-passthrough-test.js +0 -299
- package/test/memory-cleanup-test.js +0 -144
- package/test/memory-leak-test.js +0 -667
- package/test/parser-limits-test.js +0 -292
- package/test/proxy-connection-test.js +0 -738
- package/test/reliability-improvements-test.js +0 -548
- package/test/search-compiler-test.js +0 -1300
- package/test/search-test.js +0 -329
- package/test/special-use-test.js +0 -418
- package/test/starttls-injection-test.js +0 -181
- package/test/tag-correlation-test.js +0 -333
- package/test/timer-policy-test.js +0 -227
- package/test/token-parser-test.js +0 -456
- package/test/tools-test.js +0 -2013
- package/test/unhandled-rejection-test.js +0 -593
|
@@ -1,1300 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
const { searchCompiler } = require('../lib/search-compiler');
|
|
4
|
-
|
|
5
|
-
// Mock mailbox for testing
|
|
6
|
-
let createMockMailbox = () => ({
|
|
7
|
-
flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft', '$CustomFlag']),
|
|
8
|
-
permanentFlags: new Set(['\\*'])
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
// Helper to create mock connection with customizable capabilities
|
|
12
|
-
let createMockConnection = (options = {}) => ({
|
|
13
|
-
capabilities: new Map(options.capabilities || [['IMAP4rev1', true]]),
|
|
14
|
-
enabled: new Set(options.enabled || []),
|
|
15
|
-
mailbox: options.mailbox || createMockMailbox()
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
// Helper to find attribute by value (recurses into sub-arrays for parenthesized groups)
|
|
19
|
-
let findAttr = (attrs, value) => {
|
|
20
|
-
for (let a of attrs) {
|
|
21
|
-
if (Array.isArray(a)) {
|
|
22
|
-
let found = findAttr(a, value);
|
|
23
|
-
if (found) return found;
|
|
24
|
-
} else if (a.value === value) {
|
|
25
|
-
return a;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return undefined;
|
|
29
|
-
};
|
|
30
|
-
let hasAttr = (attrs, value) => !!findAttr(attrs, value);
|
|
31
|
-
|
|
32
|
-
// ============================================
|
|
33
|
-
// Basic functionality tests
|
|
34
|
-
// ============================================
|
|
35
|
-
|
|
36
|
-
module.exports['Search Compiler: Basic functionality'] = test => {
|
|
37
|
-
let connection = createMockConnection();
|
|
38
|
-
|
|
39
|
-
test.doesNotThrow(() => {
|
|
40
|
-
let compiled = searchCompiler(connection, { seen: false });
|
|
41
|
-
test.ok(Array.isArray(compiled));
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test.done();
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
module.exports['Search Compiler: Empty query'] = test => {
|
|
48
|
-
let connection = createMockConnection();
|
|
49
|
-
let compiled = searchCompiler(connection, {});
|
|
50
|
-
|
|
51
|
-
test.ok(Array.isArray(compiled));
|
|
52
|
-
test.equal(compiled.length, 0);
|
|
53
|
-
test.done();
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
module.exports['Search Compiler: Null/undefined query'] = test => {
|
|
57
|
-
let connection = createMockConnection();
|
|
58
|
-
|
|
59
|
-
let compiled1 = searchCompiler(connection, null);
|
|
60
|
-
test.ok(Array.isArray(compiled1));
|
|
61
|
-
|
|
62
|
-
let compiled2 = searchCompiler(connection, undefined);
|
|
63
|
-
test.ok(Array.isArray(compiled2));
|
|
64
|
-
|
|
65
|
-
test.done();
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// ============================================
|
|
69
|
-
// SEQ (sequence) tests
|
|
70
|
-
// ============================================
|
|
71
|
-
|
|
72
|
-
module.exports['Search Compiler: SEQ with string'] = test => {
|
|
73
|
-
let connection = createMockConnection();
|
|
74
|
-
let compiled = searchCompiler(connection, { seq: '1:100' });
|
|
75
|
-
|
|
76
|
-
test.ok(hasAttr(compiled, '1:100'));
|
|
77
|
-
let seqAttr = findAttr(compiled, '1:100');
|
|
78
|
-
test.equal(seqAttr.type, 'SEQUENCE');
|
|
79
|
-
test.done();
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
module.exports['Search Compiler: SEQ with number'] = test => {
|
|
83
|
-
let connection = createMockConnection();
|
|
84
|
-
let compiled = searchCompiler(connection, { seq: 42 });
|
|
85
|
-
|
|
86
|
-
test.ok(hasAttr(compiled, '42'));
|
|
87
|
-
test.done();
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
module.exports['Search Compiler: SEQ passes invalid values through to the compiler guard'] = test => {
|
|
91
|
-
let connection = createMockConnection();
|
|
92
|
-
|
|
93
|
-
// An invalid sequence string used to be dropped silently here, which turned the
|
|
94
|
-
// filter into an unrestricted search matching every message. It is now emitted as
|
|
95
|
-
// a SEQUENCE token so the IMAP compiler rejects it with a coded error instead.
|
|
96
|
-
let compiled = searchCompiler(connection, { seq: '1 2 3' });
|
|
97
|
-
let seqAttr = compiled.find(a => a.type === 'SEQUENCE');
|
|
98
|
-
test.ok(seqAttr, 'the sequence value must not be dropped');
|
|
99
|
-
test.equal(seqAttr.value, '1 2 3');
|
|
100
|
-
|
|
101
|
-
// Junk values pass through too, so they fail loudly at the compiler instead of
|
|
102
|
-
// silently widening the search
|
|
103
|
-
let junk = searchCompiler(connection, { seq: {} });
|
|
104
|
-
test.ok(
|
|
105
|
-
junk.find(a => a.type === 'SEQUENCE'),
|
|
106
|
-
'a junk value must not be dropped'
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
// Zero is not a valid IMAP sequence number but it is a valid filter value,
|
|
110
|
-
// so it must reach the compiler guard rather than be dropped as falsy
|
|
111
|
-
let zero = searchCompiler(connection, { seq: 0 });
|
|
112
|
-
test.equal(zero.find(a => a.type === 'SEQUENCE').value, '0', 'zero must not be dropped');
|
|
113
|
-
|
|
114
|
-
test.done();
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
module.exports['Search Compiler: SEQ array compiles to a single comma-joined set'] = test => {
|
|
118
|
-
let connection = createMockConnection();
|
|
119
|
-
let compiled = searchCompiler(connection, { seq: [1, 3] });
|
|
120
|
-
|
|
121
|
-
let seqAttr = compiled.find(a => a.type === 'SEQUENCE');
|
|
122
|
-
test.ok(seqAttr, 'array must produce a sequence set');
|
|
123
|
-
test.equal(seqAttr.value, '1,3');
|
|
124
|
-
test.done();
|
|
125
|
-
};
|
|
126
|
-
|
|
127
|
-
module.exports['Search Compiler: SEQ empty string compiles to nothing'] = test => {
|
|
128
|
-
let connection = createMockConnection();
|
|
129
|
-
let compiled = searchCompiler(connection, { seq: '' });
|
|
130
|
-
|
|
131
|
-
test.ok(!compiled.find(a => a.type === 'SEQUENCE'), 'an empty set adds no SEQUENCE attribute');
|
|
132
|
-
test.equal(compiled.length, 0);
|
|
133
|
-
test.done();
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// ============================================
|
|
137
|
-
// Boolean flag tests
|
|
138
|
-
// ============================================
|
|
139
|
-
|
|
140
|
-
module.exports['Search Compiler: SEEN flag true'] = test => {
|
|
141
|
-
let connection = createMockConnection();
|
|
142
|
-
let compiled = searchCompiler(connection, { seen: true });
|
|
143
|
-
|
|
144
|
-
test.ok(hasAttr(compiled, 'SEEN'));
|
|
145
|
-
test.done();
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
module.exports['Search Compiler: SEEN flag false adds UNSEEN'] = test => {
|
|
149
|
-
let connection = createMockConnection();
|
|
150
|
-
let compiled = searchCompiler(connection, { seen: false });
|
|
151
|
-
|
|
152
|
-
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
153
|
-
test.done();
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
module.exports['Search Compiler: UNSEEN flag false adds SEEN'] = test => {
|
|
157
|
-
let connection = createMockConnection();
|
|
158
|
-
let compiled = searchCompiler(connection, { unseen: false });
|
|
159
|
-
|
|
160
|
-
test.ok(hasAttr(compiled, 'SEEN'));
|
|
161
|
-
test.done();
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
module.exports['Search Compiler: All boolean flags'] = test => {
|
|
165
|
-
let connection = createMockConnection();
|
|
166
|
-
|
|
167
|
-
// Test all toggleable flags
|
|
168
|
-
let flags = ['answered', 'deleted', 'draft', 'flagged', 'seen'];
|
|
169
|
-
flags.forEach(flag => {
|
|
170
|
-
let compiled = searchCompiler(connection, { [flag]: true });
|
|
171
|
-
test.ok(hasAttr(compiled, flag.toUpperCase()), `${flag} should be present`);
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
test.done();
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
module.exports['Search Compiler: UN-prefixed flags'] = test => {
|
|
178
|
-
let connection = createMockConnection();
|
|
179
|
-
|
|
180
|
-
let compiled = searchCompiler(connection, {
|
|
181
|
-
unanswered: true,
|
|
182
|
-
undeleted: true,
|
|
183
|
-
undraft: true,
|
|
184
|
-
unflagged: true
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
test.ok(hasAttr(compiled, 'UNANSWERED'));
|
|
188
|
-
test.ok(hasAttr(compiled, 'UNDELETED'));
|
|
189
|
-
test.ok(hasAttr(compiled, 'UNDRAFT'));
|
|
190
|
-
test.ok(hasAttr(compiled, 'UNFLAGGED'));
|
|
191
|
-
test.done();
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// ============================================
|
|
195
|
-
// Simple boolean flags (ALL, NEW, OLD, RECENT)
|
|
196
|
-
// ============================================
|
|
197
|
-
|
|
198
|
-
module.exports['Search Compiler: ALL flag'] = test => {
|
|
199
|
-
let connection = createMockConnection();
|
|
200
|
-
let compiled = searchCompiler(connection, { all: true });
|
|
201
|
-
|
|
202
|
-
test.ok(hasAttr(compiled, 'ALL'));
|
|
203
|
-
test.done();
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
module.exports['Search Compiler: NEW flag'] = test => {
|
|
207
|
-
let connection = createMockConnection();
|
|
208
|
-
let compiled = searchCompiler(connection, { new: true });
|
|
209
|
-
|
|
210
|
-
test.ok(hasAttr(compiled, 'NEW'));
|
|
211
|
-
test.done();
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
module.exports['Search Compiler: OLD flag'] = test => {
|
|
215
|
-
let connection = createMockConnection();
|
|
216
|
-
let compiled = searchCompiler(connection, { old: true });
|
|
217
|
-
|
|
218
|
-
test.ok(hasAttr(compiled, 'OLD'));
|
|
219
|
-
test.done();
|
|
220
|
-
};
|
|
221
|
-
|
|
222
|
-
module.exports['Search Compiler: RECENT flag'] = test => {
|
|
223
|
-
let connection = createMockConnection();
|
|
224
|
-
let compiled = searchCompiler(connection, { recent: true });
|
|
225
|
-
|
|
226
|
-
test.ok(hasAttr(compiled, 'RECENT'));
|
|
227
|
-
test.done();
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
module.exports['Search Compiler: NEW/OLD/RECENT throw on rev2 sessions'] = test => {
|
|
231
|
-
// IMAP4rev2 (RFC 9051) removed the \Recent flag and these search keys -
|
|
232
|
-
// a rev2 server would reject the whole search with a tagged BAD
|
|
233
|
-
let connection = createMockConnection({ enabled: ['IMAP4REV2'] });
|
|
234
|
-
for (let key of ['new', 'old', 'recent']) {
|
|
235
|
-
try {
|
|
236
|
-
searchCompiler(connection, { [key]: true });
|
|
237
|
-
test.ok(false, `Should have thrown for ${key}`);
|
|
238
|
-
} catch (err) {
|
|
239
|
-
test.equal(err.code, 'MissingServerExtension');
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Falsy values compile to nothing and must not throw
|
|
243
|
-
test.equal(searchCompiler(connection, { recent: false }).length, 0);
|
|
244
|
-
// ALL is still part of the rev2 grammar
|
|
245
|
-
test.ok(hasAttr(searchCompiler(connection, { all: true }), 'ALL'));
|
|
246
|
-
test.done();
|
|
247
|
-
};
|
|
248
|
-
|
|
249
|
-
module.exports['Search Compiler: Simple flags ignored when falsy'] = test => {
|
|
250
|
-
let connection = createMockConnection();
|
|
251
|
-
let compiled = searchCompiler(connection, {
|
|
252
|
-
all: false,
|
|
253
|
-
new: false,
|
|
254
|
-
old: false,
|
|
255
|
-
recent: false
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
test.equal(compiled.length, 0);
|
|
259
|
-
test.done();
|
|
260
|
-
};
|
|
261
|
-
|
|
262
|
-
// ============================================
|
|
263
|
-
// Numeric comparison tests
|
|
264
|
-
// ============================================
|
|
265
|
-
|
|
266
|
-
module.exports['Search Compiler: LARGER'] = test => {
|
|
267
|
-
let connection = createMockConnection();
|
|
268
|
-
let compiled = searchCompiler(connection, { larger: 10000 });
|
|
269
|
-
|
|
270
|
-
test.ok(hasAttr(compiled, 'LARGER'));
|
|
271
|
-
test.ok(hasAttr(compiled, '10000'));
|
|
272
|
-
test.done();
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
module.exports['Search Compiler: SMALLER'] = test => {
|
|
276
|
-
let connection = createMockConnection();
|
|
277
|
-
let compiled = searchCompiler(connection, { smaller: 5000 });
|
|
278
|
-
|
|
279
|
-
test.ok(hasAttr(compiled, 'SMALLER'));
|
|
280
|
-
test.ok(hasAttr(compiled, '5000'));
|
|
281
|
-
test.done();
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
module.exports['Search Compiler: MODSEQ'] = test => {
|
|
285
|
-
let connection = createMockConnection();
|
|
286
|
-
let compiled = searchCompiler(connection, { modseq: 123456 });
|
|
287
|
-
|
|
288
|
-
test.ok(hasAttr(compiled, 'MODSEQ'));
|
|
289
|
-
test.ok(hasAttr(compiled, '123456'));
|
|
290
|
-
test.done();
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
module.exports['Search Compiler: Numeric ignores falsy values'] = test => {
|
|
294
|
-
let connection = createMockConnection();
|
|
295
|
-
let compiled = searchCompiler(connection, {
|
|
296
|
-
larger: 0,
|
|
297
|
-
smaller: null,
|
|
298
|
-
modseq: undefined
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
test.equal(compiled.length, 0);
|
|
302
|
-
test.done();
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// ============================================
|
|
306
|
-
// Text search tests
|
|
307
|
-
// ============================================
|
|
308
|
-
|
|
309
|
-
module.exports['Search Compiler: FROM'] = test => {
|
|
310
|
-
let connection = createMockConnection();
|
|
311
|
-
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
312
|
-
|
|
313
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
314
|
-
test.ok(hasAttr(compiled, 'user@example.com'));
|
|
315
|
-
test.done();
|
|
316
|
-
};
|
|
317
|
-
|
|
318
|
-
module.exports['Search Compiler: TO'] = test => {
|
|
319
|
-
let connection = createMockConnection();
|
|
320
|
-
let compiled = searchCompiler(connection, { to: 'recipient@example.com' });
|
|
321
|
-
|
|
322
|
-
test.ok(hasAttr(compiled, 'TO'));
|
|
323
|
-
test.ok(hasAttr(compiled, 'recipient@example.com'));
|
|
324
|
-
test.done();
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
module.exports['Search Compiler: CC'] = test => {
|
|
328
|
-
let connection = createMockConnection();
|
|
329
|
-
let compiled = searchCompiler(connection, { cc: 'cc@example.com' });
|
|
330
|
-
|
|
331
|
-
test.ok(hasAttr(compiled, 'CC'));
|
|
332
|
-
test.done();
|
|
333
|
-
};
|
|
334
|
-
|
|
335
|
-
module.exports['Search Compiler: BCC'] = test => {
|
|
336
|
-
let connection = createMockConnection();
|
|
337
|
-
let compiled = searchCompiler(connection, { bcc: 'bcc@example.com' });
|
|
338
|
-
|
|
339
|
-
test.ok(hasAttr(compiled, 'BCC'));
|
|
340
|
-
test.done();
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
module.exports['Search Compiler: SUBJECT'] = test => {
|
|
344
|
-
let connection = createMockConnection();
|
|
345
|
-
let compiled = searchCompiler(connection, { subject: 'Test Subject' });
|
|
346
|
-
|
|
347
|
-
test.ok(hasAttr(compiled, 'SUBJECT'));
|
|
348
|
-
test.ok(hasAttr(compiled, 'Test Subject'));
|
|
349
|
-
test.done();
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
module.exports['Search Compiler: BODY'] = test => {
|
|
353
|
-
let connection = createMockConnection();
|
|
354
|
-
let compiled = searchCompiler(connection, { body: 'search text' });
|
|
355
|
-
|
|
356
|
-
test.ok(hasAttr(compiled, 'BODY'));
|
|
357
|
-
test.ok(hasAttr(compiled, 'search text'));
|
|
358
|
-
test.done();
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
module.exports['Search Compiler: TEXT'] = test => {
|
|
362
|
-
let connection = createMockConnection();
|
|
363
|
-
let compiled = searchCompiler(connection, { text: 'full text search' });
|
|
364
|
-
|
|
365
|
-
test.ok(hasAttr(compiled, 'TEXT'));
|
|
366
|
-
test.ok(hasAttr(compiled, 'full text search'));
|
|
367
|
-
test.done();
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
module.exports['Search Compiler: Text fields ignore falsy'] = test => {
|
|
371
|
-
let connection = createMockConnection();
|
|
372
|
-
let compiled = searchCompiler(connection, {
|
|
373
|
-
from: '',
|
|
374
|
-
to: null,
|
|
375
|
-
subject: undefined
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
test.equal(compiled.length, 0);
|
|
379
|
-
test.done();
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
// ============================================
|
|
383
|
-
// UID tests
|
|
384
|
-
// ============================================
|
|
385
|
-
|
|
386
|
-
module.exports['Search Compiler: UID with string'] = test => {
|
|
387
|
-
let connection = createMockConnection();
|
|
388
|
-
let compiled = searchCompiler(connection, { uid: '1:*' });
|
|
389
|
-
|
|
390
|
-
test.ok(hasAttr(compiled, 'UID'));
|
|
391
|
-
let uidValueAttr = compiled.find(a => a.value === '1:*');
|
|
392
|
-
test.equal(uidValueAttr.type, 'SEQUENCE');
|
|
393
|
-
test.done();
|
|
394
|
-
};
|
|
395
|
-
|
|
396
|
-
module.exports['Search Compiler: UID with number'] = test => {
|
|
397
|
-
let connection = createMockConnection();
|
|
398
|
-
let compiled = searchCompiler(connection, { uid: 12345 });
|
|
399
|
-
|
|
400
|
-
test.ok(hasAttr(compiled, 'UID'));
|
|
401
|
-
test.ok(hasAttr(compiled, '12345'));
|
|
402
|
-
test.done();
|
|
403
|
-
};
|
|
404
|
-
|
|
405
|
-
module.exports['Search Compiler: UID array compiles to a single comma-joined set'] = test => {
|
|
406
|
-
let connection = createMockConnection();
|
|
407
|
-
let compiled = searchCompiler(connection, { uid: [5, 7, 9] });
|
|
408
|
-
|
|
409
|
-
// Separate tokens ("UID 5 7 9") would be parsed by the server as extra
|
|
410
|
-
// sequence-number search keys ANDed to the query - the array means the set {5,7,9}
|
|
411
|
-
test.ok(hasAttr(compiled, 'UID'));
|
|
412
|
-
let uidValueAttr = compiled.find(a => a.value === '5,7,9');
|
|
413
|
-
test.ok(uidValueAttr, 'array must be joined into one sequence set');
|
|
414
|
-
test.equal(uidValueAttr.type, 'SEQUENCE');
|
|
415
|
-
test.done();
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
module.exports['Search Compiler: UID accepts the SEARCHRES $ marker'] = test => {
|
|
419
|
-
let connection = createMockConnection();
|
|
420
|
-
let compiled = searchCompiler(connection, { uid: '$' });
|
|
421
|
-
|
|
422
|
-
test.ok(hasAttr(compiled, 'UID'));
|
|
423
|
-
let uidValueAttr = compiled.find(a => a.value === '$');
|
|
424
|
-
test.ok(uidValueAttr, 'the saved-result marker must pass through');
|
|
425
|
-
test.equal(uidValueAttr.type, 'SEQUENCE');
|
|
426
|
-
test.done();
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
// ============================================
|
|
430
|
-
// EMAILID / THREADID tests
|
|
431
|
-
// ============================================
|
|
432
|
-
|
|
433
|
-
module.exports['Search Compiler: EMAILID with OBJECTID'] = test => {
|
|
434
|
-
let connection = createMockConnection({
|
|
435
|
-
capabilities: [['OBJECTID', true]]
|
|
436
|
-
});
|
|
437
|
-
let compiled = searchCompiler(connection, { emailId: 'M1234567890' });
|
|
438
|
-
|
|
439
|
-
test.ok(hasAttr(compiled, 'EMAILID'));
|
|
440
|
-
test.ok(hasAttr(compiled, 'M1234567890'));
|
|
441
|
-
test.done();
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
module.exports['Search Compiler: EMAILID falls back to X-GM-MSGID'] = test => {
|
|
445
|
-
let connection = createMockConnection({
|
|
446
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
447
|
-
});
|
|
448
|
-
let compiled = searchCompiler(connection, { emailId: '1234567890' });
|
|
449
|
-
|
|
450
|
-
test.ok(hasAttr(compiled, 'X-GM-MSGID'));
|
|
451
|
-
test.ok(hasAttr(compiled, '1234567890'));
|
|
452
|
-
test.done();
|
|
453
|
-
};
|
|
454
|
-
|
|
455
|
-
module.exports['Search Compiler: EMAILID ignored without capability'] = test => {
|
|
456
|
-
let connection = createMockConnection();
|
|
457
|
-
let compiled = searchCompiler(connection, { emailId: '12345' });
|
|
458
|
-
|
|
459
|
-
test.equal(compiled.length, 0);
|
|
460
|
-
test.done();
|
|
461
|
-
};
|
|
462
|
-
|
|
463
|
-
module.exports['Search Compiler: THREADID with OBJECTID'] = test => {
|
|
464
|
-
let connection = createMockConnection({
|
|
465
|
-
capabilities: [['OBJECTID', true]]
|
|
466
|
-
});
|
|
467
|
-
let compiled = searchCompiler(connection, { threadId: 'T1234567890' });
|
|
468
|
-
|
|
469
|
-
test.ok(hasAttr(compiled, 'THREADID'));
|
|
470
|
-
test.ok(hasAttr(compiled, 'T1234567890'));
|
|
471
|
-
test.done();
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
module.exports['Search Compiler: THREADID falls back to X-GM-THRID'] = test => {
|
|
475
|
-
let connection = createMockConnection({
|
|
476
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
477
|
-
});
|
|
478
|
-
let compiled = searchCompiler(connection, { threadId: '9876543210' });
|
|
479
|
-
|
|
480
|
-
test.ok(hasAttr(compiled, 'X-GM-THRID'));
|
|
481
|
-
test.ok(hasAttr(compiled, '9876543210'));
|
|
482
|
-
test.done();
|
|
483
|
-
};
|
|
484
|
-
|
|
485
|
-
// ============================================
|
|
486
|
-
// Gmail raw search tests
|
|
487
|
-
// ============================================
|
|
488
|
-
|
|
489
|
-
module.exports['Search Compiler: GMRAW with X-GM-EXT-1'] = test => {
|
|
490
|
-
let connection = createMockConnection({
|
|
491
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
492
|
-
});
|
|
493
|
-
let compiled = searchCompiler(connection, { gmraw: 'in:inbox is:unread' });
|
|
494
|
-
|
|
495
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
496
|
-
test.ok(hasAttr(compiled, 'in:inbox is:unread'));
|
|
497
|
-
test.done();
|
|
498
|
-
};
|
|
499
|
-
|
|
500
|
-
module.exports['Search Compiler: GMAILRAW alias'] = test => {
|
|
501
|
-
let connection = createMockConnection({
|
|
502
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
503
|
-
});
|
|
504
|
-
let compiled = searchCompiler(connection, { gmailraw: 'has:attachment' });
|
|
505
|
-
|
|
506
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
507
|
-
test.ok(hasAttr(compiled, 'has:attachment'));
|
|
508
|
-
test.done();
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
module.exports['Search Compiler: GMRAW throws without capability'] = test => {
|
|
512
|
-
let connection = createMockConnection();
|
|
513
|
-
|
|
514
|
-
try {
|
|
515
|
-
searchCompiler(connection, { gmraw: 'test' });
|
|
516
|
-
test.ok(false, 'Should have thrown');
|
|
517
|
-
} catch (err) {
|
|
518
|
-
test.equal(err.code, 'MissingServerExtension');
|
|
519
|
-
test.ok(err.message.includes('X-GM-EXT-1'));
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
test.done();
|
|
523
|
-
};
|
|
524
|
-
|
|
525
|
-
// ============================================
|
|
526
|
-
// Gmail label search tests
|
|
527
|
-
// ============================================
|
|
528
|
-
|
|
529
|
-
module.exports['Search Compiler: LABELS has compiles to label: via X-GM-RAW'] = test => {
|
|
530
|
-
let connection = createMockConnection({
|
|
531
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
532
|
-
});
|
|
533
|
-
let compiled = searchCompiler(connection, { labels: { has: ['Horizon'] } });
|
|
534
|
-
|
|
535
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
536
|
-
test.ok(hasAttr(compiled, 'label:Horizon'));
|
|
537
|
-
test.done();
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
module.exports['Search Compiler: LABELS not compiles to -label: via X-GM-RAW'] = test => {
|
|
541
|
-
let connection = createMockConnection({
|
|
542
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
543
|
-
});
|
|
544
|
-
let compiled = searchCompiler(connection, { labels: { not: ['Horizon'] } });
|
|
545
|
-
|
|
546
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
547
|
-
test.ok(hasAttr(compiled, '-label:Horizon'));
|
|
548
|
-
test.done();
|
|
549
|
-
};
|
|
550
|
-
|
|
551
|
-
module.exports['Search Compiler: LABELS with a non-object value is ignored'] = test => {
|
|
552
|
-
let connection = createMockConnection();
|
|
553
|
-
|
|
554
|
-
// Neither a falsy value nor a plain string is a { has, not } filter - both
|
|
555
|
-
// must compile to nothing even without the Gmail extension
|
|
556
|
-
let compiledNull = searchCompiler(connection, { labels: null });
|
|
557
|
-
test.equal(compiledNull.length, 0);
|
|
558
|
-
|
|
559
|
-
let compiledString = searchCompiler(connection, { labels: 'Horizon' });
|
|
560
|
-
test.equal(compiledString.length, 0);
|
|
561
|
-
test.done();
|
|
562
|
-
};
|
|
563
|
-
|
|
564
|
-
module.exports['Search Compiler: LABELS has and not combined'] = test => {
|
|
565
|
-
let connection = createMockConnection({
|
|
566
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
567
|
-
});
|
|
568
|
-
let compiled = searchCompiler(connection, { labels: { has: ['Imported'], not: ['Horizon'] } });
|
|
569
|
-
|
|
570
|
-
test.ok(hasAttr(compiled, 'label:Imported -label:Horizon'));
|
|
571
|
-
test.done();
|
|
572
|
-
};
|
|
573
|
-
|
|
574
|
-
module.exports['Search Compiler: LABELS quotes multi-word names'] = test => {
|
|
575
|
-
let connection = createMockConnection({
|
|
576
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
577
|
-
});
|
|
578
|
-
let compiled = searchCompiler(connection, { labels: { has: ['Some Label'] } });
|
|
579
|
-
|
|
580
|
-
test.ok(hasAttr(compiled, 'label:"Some Label"'));
|
|
581
|
-
test.done();
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
module.exports['Search Compiler: LABELS coexists with gmraw'] = test => {
|
|
585
|
-
let connection = createMockConnection({
|
|
586
|
-
capabilities: [['X-GM-EXT-1', true]]
|
|
587
|
-
});
|
|
588
|
-
let compiled = searchCompiler(connection, { gmraw: 'has:attachment', labels: { not: ['Horizon'] } });
|
|
589
|
-
|
|
590
|
-
test.ok(hasAttr(compiled, 'has:attachment'));
|
|
591
|
-
test.ok(hasAttr(compiled, '-label:Horizon'));
|
|
592
|
-
test.done();
|
|
593
|
-
};
|
|
594
|
-
|
|
595
|
-
module.exports['Search Compiler: LABELS throws without capability'] = test => {
|
|
596
|
-
let connection = createMockConnection();
|
|
597
|
-
|
|
598
|
-
try {
|
|
599
|
-
searchCompiler(connection, { labels: { not: ['Horizon'] } });
|
|
600
|
-
test.ok(false, 'Should have thrown');
|
|
601
|
-
} catch (err) {
|
|
602
|
-
test.equal(err.code, 'MissingServerExtension');
|
|
603
|
-
test.ok(err.message.includes('X-GM-EXT-1'));
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
test.done();
|
|
607
|
-
};
|
|
608
|
-
|
|
609
|
-
module.exports['Search Compiler: empty LABELS is a no-op without capability'] = test => {
|
|
610
|
-
let connection = createMockConnection();
|
|
611
|
-
let compiled = searchCompiler(connection, { labels: {} });
|
|
612
|
-
|
|
613
|
-
test.ok(!hasAttr(compiled, 'X-GM-RAW'));
|
|
614
|
-
test.equal(compiled.length, 0);
|
|
615
|
-
test.done();
|
|
616
|
-
};
|
|
617
|
-
|
|
618
|
-
// ============================================
|
|
619
|
-
// Date search tests
|
|
620
|
-
// ============================================
|
|
621
|
-
|
|
622
|
-
module.exports['Search Compiler: SINCE'] = test => {
|
|
623
|
-
let connection = createMockConnection();
|
|
624
|
-
let compiled = searchCompiler(connection, { since: new Date('2023-06-15') });
|
|
625
|
-
|
|
626
|
-
test.ok(hasAttr(compiled, 'SINCE'));
|
|
627
|
-
test.ok(hasAttr(compiled, '15-Jun-2023'));
|
|
628
|
-
test.done();
|
|
629
|
-
};
|
|
630
|
-
|
|
631
|
-
module.exports['Search Compiler: BEFORE'] = test => {
|
|
632
|
-
let connection = createMockConnection();
|
|
633
|
-
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T00:00:00.000Z') });
|
|
634
|
-
|
|
635
|
-
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
636
|
-
test.done();
|
|
637
|
-
};
|
|
638
|
-
|
|
639
|
-
module.exports['Search Compiler: BEFORE with non-midnight time adjusts date'] = test => {
|
|
640
|
-
let connection = createMockConnection();
|
|
641
|
-
// Non-midnight time should advance to next day
|
|
642
|
-
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T12:30:00.000Z') });
|
|
643
|
-
|
|
644
|
-
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
645
|
-
// Should be 16-Jun-2023 (next day)
|
|
646
|
-
test.ok(hasAttr(compiled, '16-Jun-2023'));
|
|
647
|
-
test.done();
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
module.exports['Search Compiler: ON'] = test => {
|
|
651
|
-
let connection = createMockConnection();
|
|
652
|
-
let compiled = searchCompiler(connection, { on: new Date('2023-06-15') });
|
|
653
|
-
|
|
654
|
-
test.ok(hasAttr(compiled, 'ON'));
|
|
655
|
-
test.done();
|
|
656
|
-
};
|
|
657
|
-
|
|
658
|
-
module.exports['Search Compiler: SENTBEFORE'] = test => {
|
|
659
|
-
let connection = createMockConnection();
|
|
660
|
-
let compiled = searchCompiler(connection, { sentbefore: new Date('2023-06-15T00:00:00.000Z') });
|
|
661
|
-
|
|
662
|
-
test.ok(hasAttr(compiled, 'SENTBEFORE'));
|
|
663
|
-
test.done();
|
|
664
|
-
};
|
|
665
|
-
|
|
666
|
-
module.exports['Search Compiler: SENTON'] = test => {
|
|
667
|
-
let connection = createMockConnection();
|
|
668
|
-
let compiled = searchCompiler(connection, { senton: new Date('2023-06-15') });
|
|
669
|
-
|
|
670
|
-
test.ok(hasAttr(compiled, 'SENTON'));
|
|
671
|
-
test.done();
|
|
672
|
-
};
|
|
673
|
-
|
|
674
|
-
module.exports['Search Compiler: SENTSINCE'] = test => {
|
|
675
|
-
let connection = createMockConnection();
|
|
676
|
-
let compiled = searchCompiler(connection, { sentsince: new Date('2023-06-15') });
|
|
677
|
-
|
|
678
|
-
test.ok(hasAttr(compiled, 'SENTSINCE'));
|
|
679
|
-
test.done();
|
|
680
|
-
};
|
|
681
|
-
|
|
682
|
-
module.exports['Search Compiler: SINCE with WITHIN extension'] = test => {
|
|
683
|
-
let connection = createMockConnection({
|
|
684
|
-
capabilities: [['WITHIN', true]]
|
|
685
|
-
});
|
|
686
|
-
let recentDate = new Date(Date.now() - 3600 * 1000); // 1 hour ago
|
|
687
|
-
let compiled = searchCompiler(connection, { since: recentDate });
|
|
688
|
-
|
|
689
|
-
test.ok(hasAttr(compiled, 'YOUNGER'));
|
|
690
|
-
test.done();
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
module.exports['Search Compiler: BEFORE with WITHIN extension'] = test => {
|
|
694
|
-
let connection = createMockConnection({
|
|
695
|
-
capabilities: [['WITHIN', true]]
|
|
696
|
-
});
|
|
697
|
-
let oldDate = new Date(Date.now() - 86400 * 1000); // 1 day ago
|
|
698
|
-
let compiled = searchCompiler(connection, { before: oldDate });
|
|
699
|
-
|
|
700
|
-
test.ok(hasAttr(compiled, 'OLDER'));
|
|
701
|
-
test.done();
|
|
702
|
-
};
|
|
703
|
-
|
|
704
|
-
module.exports['Search Compiler: Date with invalid value ignored'] = test => {
|
|
705
|
-
let connection = createMockConnection();
|
|
706
|
-
let compiled = searchCompiler(connection, { since: 'invalid-date' });
|
|
707
|
-
|
|
708
|
-
// formatDate returns undefined for invalid dates
|
|
709
|
-
test.equal(compiled.length, 0);
|
|
710
|
-
test.done();
|
|
711
|
-
};
|
|
712
|
-
|
|
713
|
-
module.exports['Search Compiler: all date keys ignore an invalid Date object'] = test => {
|
|
714
|
-
// An invalid Date is still a Date, so a plain Date brand check let it through:
|
|
715
|
-
// BEFORE/SENTBEFORE threw RangeError from toISOString(), and with WITHIN
|
|
716
|
-
// advertised BEFORE/SINCE compiled a literal "OLDER NaN"/"YOUNGER NaN" token
|
|
717
|
-
for (let capabilities of [[['IMAP4rev1', true]], [['WITHIN', true]]]) {
|
|
718
|
-
for (let key of ['before', 'since', 'on', 'sentBefore', 'sentOn', 'sentSince']) {
|
|
719
|
-
let connection = createMockConnection({ capabilities });
|
|
720
|
-
let compiled = searchCompiler(connection, { [key]: new Date('not-a-date') });
|
|
721
|
-
|
|
722
|
-
test.deepEqual(compiled, [], `${key} should compile to no attributes`);
|
|
723
|
-
}
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
test.done();
|
|
727
|
-
};
|
|
728
|
-
|
|
729
|
-
module.exports['Search Compiler: invalid date drops only its own criterion'] = test => {
|
|
730
|
-
let connection = createMockConnection();
|
|
731
|
-
let compiled = searchCompiler(connection, { seen: true, before: new Date('not-a-date') });
|
|
732
|
-
|
|
733
|
-
test.deepEqual(compiled, [{ type: 'ATOM', value: 'SEEN' }]);
|
|
734
|
-
test.done();
|
|
735
|
-
};
|
|
736
|
-
|
|
737
|
-
module.exports['Search Compiler: date string and Date object compile alike for BEFORE'] = test => {
|
|
738
|
-
let asDate = searchCompiler(createMockConnection(), { before: new Date('2023-06-15T12:30:00.000Z') });
|
|
739
|
-
let asString = searchCompiler(createMockConnection(), { before: '2023-06-15T12:30:00.000Z' });
|
|
740
|
-
|
|
741
|
-
// Both input forms are documented, so both must get the +24h shift that makes
|
|
742
|
-
// same-day BEFORE+SINCE ranges match. The string form used to skip it.
|
|
743
|
-
test.deepEqual(asString, asDate);
|
|
744
|
-
test.ok(hasAttr(asString, '16-Jun-2023'));
|
|
745
|
-
test.done();
|
|
746
|
-
};
|
|
747
|
-
|
|
748
|
-
module.exports['Search Compiler: date string and Date object compile alike for WITHIN'] = test => {
|
|
749
|
-
let connection = createMockConnection({ capabilities: [['WITHIN', true]] });
|
|
750
|
-
let recentDate = new Date(Date.now() - 3600 * 1000); // 1 hour ago
|
|
751
|
-
let asDate = searchCompiler(connection, { since: recentDate });
|
|
752
|
-
let asString = searchCompiler(connection, { since: recentDate.toISOString() });
|
|
753
|
-
|
|
754
|
-
// The string form used to skip the WITHIN shortcut and compile SINCE instead
|
|
755
|
-
test.ok(hasAttr(asDate, 'YOUNGER'));
|
|
756
|
-
test.ok(hasAttr(asString, 'YOUNGER'));
|
|
757
|
-
// The keyword atom is followed by its value token
|
|
758
|
-
let seconds = attrs => Number(attrs[1].value);
|
|
759
|
-
// Both are measured against Date.now() at compile time, so allow a small drift
|
|
760
|
-
test.ok(Math.abs(seconds(asString) - seconds(asDate)) <= 1);
|
|
761
|
-
test.done();
|
|
762
|
-
};
|
|
763
|
-
|
|
764
|
-
// ============================================
|
|
765
|
-
// KEYWORD tests
|
|
766
|
-
// ============================================
|
|
767
|
-
|
|
768
|
-
module.exports['Search Compiler: KEYWORD'] = test => {
|
|
769
|
-
let connection = createMockConnection();
|
|
770
|
-
let compiled = searchCompiler(connection, { keyword: '$CustomFlag' });
|
|
771
|
-
|
|
772
|
-
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
773
|
-
test.ok(hasAttr(compiled, '$CustomFlag'));
|
|
774
|
-
test.done();
|
|
775
|
-
};
|
|
776
|
-
|
|
777
|
-
module.exports['Search Compiler: UNKEYWORD'] = test => {
|
|
778
|
-
let connection = createMockConnection();
|
|
779
|
-
let compiled = searchCompiler(connection, { unkeyword: '$CustomFlag' });
|
|
780
|
-
|
|
781
|
-
test.ok(hasAttr(compiled, 'UNKEYWORD'));
|
|
782
|
-
test.done();
|
|
783
|
-
};
|
|
784
|
-
|
|
785
|
-
module.exports['Search Compiler: KEYWORD with standard flag'] = test => {
|
|
786
|
-
let connection = createMockConnection();
|
|
787
|
-
let compiled = searchCompiler(connection, { keyword: '\\Seen' });
|
|
788
|
-
|
|
789
|
-
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
790
|
-
test.ok(hasAttr(compiled, '\\Seen'));
|
|
791
|
-
test.done();
|
|
792
|
-
};
|
|
793
|
-
|
|
794
|
-
// ============================================
|
|
795
|
-
// HEADER tests
|
|
796
|
-
// ============================================
|
|
797
|
-
|
|
798
|
-
module.exports['Search Compiler: HEADER with value'] = test => {
|
|
799
|
-
let connection = createMockConnection();
|
|
800
|
-
let compiled = searchCompiler(connection, {
|
|
801
|
-
header: {
|
|
802
|
-
'X-Custom-Header': 'custom-value'
|
|
803
|
-
}
|
|
804
|
-
});
|
|
805
|
-
|
|
806
|
-
test.ok(hasAttr(compiled, 'HEADER'));
|
|
807
|
-
test.ok(hasAttr(compiled, 'X-CUSTOM-HEADER'));
|
|
808
|
-
test.ok(hasAttr(compiled, 'custom-value'));
|
|
809
|
-
test.done();
|
|
810
|
-
};
|
|
811
|
-
|
|
812
|
-
module.exports['Search Compiler: HEADER existence check'] = test => {
|
|
813
|
-
let connection = createMockConnection();
|
|
814
|
-
let compiled = searchCompiler(connection, {
|
|
815
|
-
header: {
|
|
816
|
-
'X-Priority': true // Check header exists
|
|
817
|
-
}
|
|
818
|
-
});
|
|
819
|
-
|
|
820
|
-
test.ok(hasAttr(compiled, 'HEADER'));
|
|
821
|
-
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
822
|
-
test.ok(hasAttr(compiled, '')); // Empty string for existence check
|
|
823
|
-
test.done();
|
|
824
|
-
};
|
|
825
|
-
|
|
826
|
-
module.exports['Search Compiler: HEADER multiple headers'] = test => {
|
|
827
|
-
let connection = createMockConnection();
|
|
828
|
-
let compiled = searchCompiler(connection, {
|
|
829
|
-
header: {
|
|
830
|
-
'X-Mailer': 'Outlook',
|
|
831
|
-
'X-Priority': '1'
|
|
832
|
-
}
|
|
833
|
-
});
|
|
834
|
-
|
|
835
|
-
test.ok(hasAttr(compiled, 'X-MAILER'));
|
|
836
|
-
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
837
|
-
test.done();
|
|
838
|
-
};
|
|
839
|
-
|
|
840
|
-
module.exports['Search Compiler: HEADER ignores non-string values'] = test => {
|
|
841
|
-
let connection = createMockConnection();
|
|
842
|
-
let compiled = searchCompiler(connection, {
|
|
843
|
-
header: {
|
|
844
|
-
'X-Number': 123,
|
|
845
|
-
'X-Null': null
|
|
846
|
-
}
|
|
847
|
-
});
|
|
848
|
-
|
|
849
|
-
// Non-string values (except true) should be skipped
|
|
850
|
-
test.ok(!hasAttr(compiled, 'X-NUMBER'));
|
|
851
|
-
test.ok(!hasAttr(compiled, 'X-NULL'));
|
|
852
|
-
test.done();
|
|
853
|
-
};
|
|
854
|
-
|
|
855
|
-
module.exports['Search Compiler: HEADER with null/invalid object'] = test => {
|
|
856
|
-
let connection = createMockConnection();
|
|
857
|
-
|
|
858
|
-
let compiled1 = searchCompiler(connection, { header: null });
|
|
859
|
-
test.equal(compiled1.length, 0);
|
|
860
|
-
|
|
861
|
-
let compiled2 = searchCompiler(connection, { header: 'not-an-object' });
|
|
862
|
-
test.equal(compiled2.length, 0);
|
|
863
|
-
|
|
864
|
-
test.done();
|
|
865
|
-
};
|
|
866
|
-
|
|
867
|
-
// ============================================
|
|
868
|
-
// NOT operator tests
|
|
869
|
-
// ============================================
|
|
870
|
-
|
|
871
|
-
module.exports['Search Compiler: NOT operator'] = test => {
|
|
872
|
-
let connection = createMockConnection();
|
|
873
|
-
let compiled = searchCompiler(connection, {
|
|
874
|
-
not: { from: 'spam@example.com' }
|
|
875
|
-
});
|
|
876
|
-
|
|
877
|
-
test.ok(hasAttr(compiled, 'NOT'));
|
|
878
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
879
|
-
test.ok(hasAttr(compiled, 'spam@example.com'));
|
|
880
|
-
test.done();
|
|
881
|
-
};
|
|
882
|
-
|
|
883
|
-
module.exports['Search Compiler: NOT with nested conditions'] = test => {
|
|
884
|
-
let connection = createMockConnection();
|
|
885
|
-
let compiled = searchCompiler(connection, {
|
|
886
|
-
not: {
|
|
887
|
-
seen: true,
|
|
888
|
-
from: 'test@example.com'
|
|
889
|
-
}
|
|
890
|
-
});
|
|
891
|
-
|
|
892
|
-
test.ok(hasAttr(compiled, 'NOT'));
|
|
893
|
-
test.ok(hasAttr(compiled, 'SEEN'));
|
|
894
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
895
|
-
// Compound NOT conditions should be wrapped in a sub-array (parenthesized)
|
|
896
|
-
// so the server treats them as a single search-key
|
|
897
|
-
test.equal(compiled[0].value, 'NOT');
|
|
898
|
-
test.ok(Array.isArray(compiled[1]), 'compound NOT should be parenthesized');
|
|
899
|
-
test.done();
|
|
900
|
-
};
|
|
901
|
-
|
|
902
|
-
module.exports['Search Compiler: NOT ignored when falsy'] = test => {
|
|
903
|
-
let connection = createMockConnection();
|
|
904
|
-
|
|
905
|
-
let compiled1 = searchCompiler(connection, { not: null });
|
|
906
|
-
test.equal(compiled1.length, 0);
|
|
907
|
-
|
|
908
|
-
let compiled2 = searchCompiler(connection, { not: false });
|
|
909
|
-
test.equal(compiled2.length, 0);
|
|
910
|
-
|
|
911
|
-
test.done();
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
// ============================================
|
|
915
|
-
// OR operator tests
|
|
916
|
-
// ============================================
|
|
917
|
-
|
|
918
|
-
module.exports['Search Compiler: OR with two conditions'] = test => {
|
|
919
|
-
let connection = createMockConnection();
|
|
920
|
-
let compiled = searchCompiler(connection, {
|
|
921
|
-
or: [{ from: 'alice@example.com' }, { from: 'bob@example.com' }]
|
|
922
|
-
});
|
|
923
|
-
|
|
924
|
-
test.ok(hasAttr(compiled, 'OR'));
|
|
925
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
926
|
-
test.ok(hasAttr(compiled, 'alice@example.com'));
|
|
927
|
-
test.ok(hasAttr(compiled, 'bob@example.com'));
|
|
928
|
-
test.done();
|
|
929
|
-
};
|
|
930
|
-
|
|
931
|
-
module.exports['Search Compiler: OR with single condition'] = test => {
|
|
932
|
-
let connection = createMockConnection();
|
|
933
|
-
let compiled = searchCompiler(connection, {
|
|
934
|
-
or: [{ from: 'only@example.com' }]
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
// Single condition should not add OR
|
|
938
|
-
test.ok(!hasAttr(compiled, 'OR'));
|
|
939
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
940
|
-
test.ok(hasAttr(compiled, 'only@example.com'));
|
|
941
|
-
test.done();
|
|
942
|
-
};
|
|
943
|
-
|
|
944
|
-
module.exports['Search Compiler: OR with three conditions'] = test => {
|
|
945
|
-
let connection = createMockConnection();
|
|
946
|
-
let compiled = searchCompiler(connection, {
|
|
947
|
-
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }]
|
|
948
|
-
});
|
|
949
|
-
|
|
950
|
-
// Should have OR for tree structure
|
|
951
|
-
test.ok(hasAttr(compiled, 'OR'));
|
|
952
|
-
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
953
|
-
test.ok(hasAttr(compiled, 'b@example.com'));
|
|
954
|
-
test.ok(hasAttr(compiled, 'c@example.com'));
|
|
955
|
-
test.done();
|
|
956
|
-
};
|
|
957
|
-
|
|
958
|
-
module.exports['Search Compiler: OR with four conditions'] = test => {
|
|
959
|
-
let connection = createMockConnection();
|
|
960
|
-
let compiled = searchCompiler(connection, {
|
|
961
|
-
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }, { from: 'd@example.com' }]
|
|
962
|
-
});
|
|
963
|
-
|
|
964
|
-
test.ok(hasAttr(compiled, 'OR'));
|
|
965
|
-
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
966
|
-
test.ok(hasAttr(compiled, 'd@example.com'));
|
|
967
|
-
test.done();
|
|
968
|
-
};
|
|
969
|
-
|
|
970
|
-
module.exports['Search Compiler: OR ignored when empty'] = test => {
|
|
971
|
-
let connection = createMockConnection();
|
|
972
|
-
|
|
973
|
-
let compiled1 = searchCompiler(connection, { or: [] });
|
|
974
|
-
test.equal(compiled1.length, 0);
|
|
975
|
-
|
|
976
|
-
let compiled2 = searchCompiler(connection, { or: null });
|
|
977
|
-
test.equal(compiled2.length, 0);
|
|
978
|
-
|
|
979
|
-
let compiled3 = searchCompiler(connection, { or: 'not-an-array' });
|
|
980
|
-
test.equal(compiled3.length, 0);
|
|
981
|
-
|
|
982
|
-
test.done();
|
|
983
|
-
};
|
|
984
|
-
|
|
985
|
-
module.exports['Search Compiler: OR with null entry in array'] = test => {
|
|
986
|
-
let connection = createMockConnection();
|
|
987
|
-
let compiled = searchCompiler(connection, {
|
|
988
|
-
or: [{ from: 'test@example.com' }, null]
|
|
989
|
-
});
|
|
990
|
-
|
|
991
|
-
// Should still process valid entries
|
|
992
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
993
|
-
test.done();
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
// ============================================
|
|
997
|
-
// Unicode / CHARSET tests
|
|
998
|
-
// ============================================
|
|
999
|
-
|
|
1000
|
-
module.exports['Search Compiler: Unicode adds CHARSET UTF-8'] = test => {
|
|
1001
|
-
let connection = createMockConnection({
|
|
1002
|
-
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
1003
|
-
});
|
|
1004
|
-
let compiled = searchCompiler(connection, { from: 'test@example.com' });
|
|
1005
|
-
|
|
1006
|
-
// No unicode, no charset
|
|
1007
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1008
|
-
|
|
1009
|
-
// With unicode
|
|
1010
|
-
let compiled2 = searchCompiler(connection, { subject: 'Test' });
|
|
1011
|
-
test.ok(!hasAttr(compiled2, 'CHARSET'));
|
|
1012
|
-
|
|
1013
|
-
test.done();
|
|
1014
|
-
};
|
|
1015
|
-
|
|
1016
|
-
module.exports['Search Compiler: Unicode in subject adds CHARSET'] = test => {
|
|
1017
|
-
let connection = createMockConnection({
|
|
1018
|
-
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
1019
|
-
});
|
|
1020
|
-
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
1021
|
-
|
|
1022
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1023
|
-
test.done();
|
|
1024
|
-
};
|
|
1025
|
-
|
|
1026
|
-
module.exports['Search Compiler: Unicode text triggers CHARSET'] = test => {
|
|
1027
|
-
let connection = createMockConnection({
|
|
1028
|
-
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
1029
|
-
});
|
|
1030
|
-
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
1031
|
-
|
|
1032
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1033
|
-
test.done();
|
|
1034
|
-
};
|
|
1035
|
-
|
|
1036
|
-
module.exports['Search Compiler: Unicode skipped when UTF8=ACCEPT enabled'] = test => {
|
|
1037
|
-
let connection = createMockConnection({
|
|
1038
|
-
enabled: new Set(['UTF8=ACCEPT'])
|
|
1039
|
-
});
|
|
1040
|
-
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
1041
|
-
|
|
1042
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1043
|
-
test.done();
|
|
1044
|
-
};
|
|
1045
|
-
|
|
1046
|
-
module.exports['Search Compiler: Unicode on rev2 without UTF8=ACCEPT still adds CHARSET UTF-8'] = test => {
|
|
1047
|
-
// RFC 9051 6.4.4: rev2 servers MUST assume UTF-8 when CHARSET is absent, and
|
|
1048
|
-
// sending CHARSET UTF-8 is "redundant" but explicitly "permitted for improved
|
|
1049
|
-
// compatibility" - pin the compiler's choice to send it until UTF8=ACCEPT is
|
|
1050
|
-
// actually ENABLEd
|
|
1051
|
-
let connection = createMockConnection({
|
|
1052
|
-
capabilities: [['IMAP4rev2', true]],
|
|
1053
|
-
enabled: new Set()
|
|
1054
|
-
});
|
|
1055
|
-
let compiled = searchCompiler(connection, { subject: 'Sõnum' });
|
|
1056
|
-
|
|
1057
|
-
let charset = findAttr(compiled, 'CHARSET');
|
|
1058
|
-
test.ok(charset, 'CHARSET prefix expected for a unicode search value');
|
|
1059
|
-
test.ok(hasAttr(compiled, 'UTF-8'));
|
|
1060
|
-
test.done();
|
|
1061
|
-
};
|
|
1062
|
-
|
|
1063
|
-
module.exports['Search Compiler: GMRAW with Unicode adds CHARSET'] = test => {
|
|
1064
|
-
let connection = createMockConnection({
|
|
1065
|
-
capabilities: [['X-GM-EXT-1', true]],
|
|
1066
|
-
enabled: new Set()
|
|
1067
|
-
});
|
|
1068
|
-
let compiled = searchCompiler(connection, { gmraw: 'test query' });
|
|
1069
|
-
|
|
1070
|
-
// ASCII query, no charset needed
|
|
1071
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1072
|
-
test.done();
|
|
1073
|
-
};
|
|
1074
|
-
|
|
1075
|
-
module.exports['Search Compiler: HEADER with Unicode adds CHARSET'] = test => {
|
|
1076
|
-
let connection = createMockConnection({
|
|
1077
|
-
enabled: new Set()
|
|
1078
|
-
});
|
|
1079
|
-
let compiled = searchCompiler(connection, {
|
|
1080
|
-
header: { Subject: 'ASCII only' }
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
1084
|
-
test.done();
|
|
1085
|
-
};
|
|
1086
|
-
|
|
1087
|
-
module.exports['Search Compiler: non-ASCII text field adds CHARSET UTF-8'] = test => {
|
|
1088
|
-
let connection = createMockConnection({
|
|
1089
|
-
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
1090
|
-
});
|
|
1091
|
-
// Non-ASCII subject must flip hasUnicode and prepend CHARSET UTF-8
|
|
1092
|
-
let compiled = searchCompiler(connection, { subject: 'résumé café' });
|
|
1093
|
-
test.ok(hasAttr(compiled, 'CHARSET'));
|
|
1094
|
-
test.ok(hasAttr(compiled, 'UTF-8'));
|
|
1095
|
-
test.done();
|
|
1096
|
-
};
|
|
1097
|
-
|
|
1098
|
-
module.exports['Search Compiler: non-ASCII body field adds CHARSET UTF-8'] = test => {
|
|
1099
|
-
let connection = createMockConnection({
|
|
1100
|
-
enabled: new Set()
|
|
1101
|
-
});
|
|
1102
|
-
let compiled = searchCompiler(connection, { body: 'Grüße' });
|
|
1103
|
-
test.ok(hasAttr(compiled, 'CHARSET'));
|
|
1104
|
-
test.done();
|
|
1105
|
-
};
|
|
1106
|
-
|
|
1107
|
-
module.exports['Search Compiler: non-ASCII GMRAW adds CHARSET UTF-8'] = test => {
|
|
1108
|
-
let connection = createMockConnection({
|
|
1109
|
-
capabilities: [['X-GM-EXT-1', true]],
|
|
1110
|
-
enabled: new Set()
|
|
1111
|
-
});
|
|
1112
|
-
let compiled = searchCompiler(connection, { gmraw: 'subject:café' });
|
|
1113
|
-
test.ok(hasAttr(compiled, 'CHARSET'));
|
|
1114
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
1115
|
-
test.done();
|
|
1116
|
-
};
|
|
1117
|
-
|
|
1118
|
-
module.exports['Search Compiler: non-ASCII LABELS adds CHARSET UTF-8'] = test => {
|
|
1119
|
-
let connection = createMockConnection({
|
|
1120
|
-
capabilities: [['X-GM-EXT-1', true]],
|
|
1121
|
-
enabled: new Set()
|
|
1122
|
-
});
|
|
1123
|
-
// The label filter compiles into an X-GM-RAW query, and a non-ASCII label name
|
|
1124
|
-
// must mark the compiled query as Unicode the same way a direct gmraw value does
|
|
1125
|
-
let compiled = searchCompiler(connection, { labels: { has: ['Tähtis'] } });
|
|
1126
|
-
test.ok(hasAttr(compiled, 'CHARSET'));
|
|
1127
|
-
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
1128
|
-
test.ok(hasAttr(compiled, 'label:Tähtis'));
|
|
1129
|
-
test.done();
|
|
1130
|
-
};
|
|
1131
|
-
|
|
1132
|
-
// ============================================
|
|
1133
|
-
// Complex query tests
|
|
1134
|
-
// ============================================
|
|
1135
|
-
|
|
1136
|
-
module.exports['Search Compiler: Complex combined query'] = test => {
|
|
1137
|
-
let connection = createMockConnection();
|
|
1138
|
-
let compiled = searchCompiler(connection, {
|
|
1139
|
-
seen: false,
|
|
1140
|
-
from: 'sender@example.com',
|
|
1141
|
-
since: new Date('2023-01-01'),
|
|
1142
|
-
larger: 1000
|
|
1143
|
-
});
|
|
1144
|
-
|
|
1145
|
-
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
1146
|
-
test.ok(hasAttr(compiled, 'FROM'));
|
|
1147
|
-
test.ok(hasAttr(compiled, 'SINCE'));
|
|
1148
|
-
test.ok(hasAttr(compiled, 'LARGER'));
|
|
1149
|
-
test.done();
|
|
1150
|
-
};
|
|
1151
|
-
|
|
1152
|
-
module.exports['Search Compiler: OR combined with other criteria'] = test => {
|
|
1153
|
-
let connection = createMockConnection();
|
|
1154
|
-
let compiled = searchCompiler(connection, {
|
|
1155
|
-
seen: true,
|
|
1156
|
-
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }]
|
|
1157
|
-
});
|
|
1158
|
-
|
|
1159
|
-
test.ok(hasAttr(compiled, 'SEEN'));
|
|
1160
|
-
test.ok(hasAttr(compiled, 'OR'));
|
|
1161
|
-
test.done();
|
|
1162
|
-
};
|
|
1163
|
-
|
|
1164
|
-
// ============================================
|
|
1165
|
-
// OR tree structure tests
|
|
1166
|
-
// ============================================
|
|
1167
|
-
|
|
1168
|
-
module.exports['Search Compiler: OR with 3 conditions builds binary tree'] = test => {
|
|
1169
|
-
let connection = createMockConnection();
|
|
1170
|
-
let compiled = searchCompiler(connection, {
|
|
1171
|
-
or: [{ from: 'alice' }, { to: 'bob' }, { subject: 'test' }]
|
|
1172
|
-
});
|
|
1173
|
-
|
|
1174
|
-
test.ok(hasAttr(compiled, 'OR'), 'should contain OR atom');
|
|
1175
|
-
test.ok(hasAttr(compiled, 'alice'), 'should contain alice');
|
|
1176
|
-
test.ok(hasAttr(compiled, 'bob'), 'should contain bob');
|
|
1177
|
-
test.ok(hasAttr(compiled, 'test'), 'should contain test');
|
|
1178
|
-
test.ok(hasAttr(compiled, 'FROM'), 'should contain FROM');
|
|
1179
|
-
test.ok(hasAttr(compiled, 'TO'), 'should contain TO');
|
|
1180
|
-
test.ok(hasAttr(compiled, 'SUBJECT'), 'should contain SUBJECT');
|
|
1181
|
-
test.done();
|
|
1182
|
-
};
|
|
1183
|
-
|
|
1184
|
-
module.exports['Search Compiler: OR with 5 conditions produces 4 OR atoms'] = test => {
|
|
1185
|
-
let connection = createMockConnection();
|
|
1186
|
-
let compiled = searchCompiler(connection, {
|
|
1187
|
-
or: [{ from: 'a' }, { from: 'b' }, { from: 'c' }, { from: 'd' }, { from: 'e' }]
|
|
1188
|
-
});
|
|
1189
|
-
|
|
1190
|
-
// 5 conditions need 4 OR atoms in a binary tree structure
|
|
1191
|
-
let orCount = compiled.filter(a => a.value === 'OR').length;
|
|
1192
|
-
test.equal(orCount, 4, 'should have exactly 4 OR atoms for 5 conditions');
|
|
1193
|
-
test.ok(hasAttr(compiled, 'a'), 'should contain a');
|
|
1194
|
-
test.ok(hasAttr(compiled, 'b'), 'should contain b');
|
|
1195
|
-
test.ok(hasAttr(compiled, 'c'), 'should contain c');
|
|
1196
|
-
test.ok(hasAttr(compiled, 'd'), 'should contain d');
|
|
1197
|
-
test.ok(hasAttr(compiled, 'e'), 'should contain e');
|
|
1198
|
-
test.done();
|
|
1199
|
-
};
|
|
1200
|
-
|
|
1201
|
-
module.exports['Search Compiler: OR with compound conditions wraps in parentheses'] = test => {
|
|
1202
|
-
let connection = createMockConnection();
|
|
1203
|
-
let compiled = searchCompiler(connection, {
|
|
1204
|
-
or: [
|
|
1205
|
-
{ to: 'a@example.com', from: 'b@example.com' },
|
|
1206
|
-
{ to: 'c@example.com', from: 'd@example.com' }
|
|
1207
|
-
]
|
|
1208
|
-
});
|
|
1209
|
-
|
|
1210
|
-
// OR should be present
|
|
1211
|
-
test.equal(compiled[0].value, 'OR');
|
|
1212
|
-
// Each compound condition should be a sub-array (parenthesized)
|
|
1213
|
-
test.ok(Array.isArray(compiled[1]), 'first compound operand should be parenthesized');
|
|
1214
|
-
test.ok(Array.isArray(compiled[2]), 'second compound operand should be parenthesized');
|
|
1215
|
-
// Check contents of parenthesized groups
|
|
1216
|
-
test.ok(hasAttr(compiled[1], 'TO'), 'first group should have TO');
|
|
1217
|
-
test.ok(hasAttr(compiled[1], 'FROM'), 'first group should have FROM');
|
|
1218
|
-
test.ok(hasAttr(compiled[2], 'TO'), 'second group should have TO');
|
|
1219
|
-
test.ok(hasAttr(compiled[2], 'FROM'), 'second group should have FROM');
|
|
1220
|
-
test.done();
|
|
1221
|
-
};
|
|
1222
|
-
|
|
1223
|
-
module.exports['Search Compiler: OR with single-key conditions stays flat'] = test => {
|
|
1224
|
-
let connection = createMockConnection();
|
|
1225
|
-
let compiled = searchCompiler(connection, {
|
|
1226
|
-
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }]
|
|
1227
|
-
});
|
|
1228
|
-
|
|
1229
|
-
// Single-key conditions should not be wrapped in sub-arrays
|
|
1230
|
-
test.equal(compiled[0].value, 'OR');
|
|
1231
|
-
test.ok(!Array.isArray(compiled[1]), 'single-key operand should not be parenthesized');
|
|
1232
|
-
test.equal(compiled[1].value, 'FROM');
|
|
1233
|
-
test.done();
|
|
1234
|
-
};
|
|
1235
|
-
|
|
1236
|
-
module.exports['Search Compiler: OR with 5 compound conditions from issue #106'] = test => {
|
|
1237
|
-
let connection = createMockConnection();
|
|
1238
|
-
let compiled = searchCompiler(connection, {
|
|
1239
|
-
or: [
|
|
1240
|
-
{ to: 'myemail@domain.com', from: '@anotherdomain.com' },
|
|
1241
|
-
{ to: 'myemail@domain.com', from: '@aseconddomain.com' },
|
|
1242
|
-
{ to: 'myemail@domain.com', from: '@athirddomain.fr' },
|
|
1243
|
-
{ to: 'anotheremail@domain.com', from: '@anotherdomain.fr' },
|
|
1244
|
-
{ to: 'anotheremail@domain.com', from: '@aseconddomain.com' }
|
|
1245
|
-
]
|
|
1246
|
-
});
|
|
1247
|
-
|
|
1248
|
-
// 5 conditions need 4 OR atoms
|
|
1249
|
-
let orCount = compiled.filter(a => a.value === 'OR').length;
|
|
1250
|
-
test.equal(orCount, 4, 'should have exactly 4 OR atoms for 5 conditions');
|
|
1251
|
-
|
|
1252
|
-
// All compound conditions should be parenthesized (sub-arrays)
|
|
1253
|
-
let subArrays = compiled.filter(a => Array.isArray(a));
|
|
1254
|
-
test.equal(subArrays.length, 5, 'should have 5 parenthesized groups');
|
|
1255
|
-
|
|
1256
|
-
// Each group should contain both TO and FROM
|
|
1257
|
-
subArrays.forEach((group, i) => {
|
|
1258
|
-
test.ok(hasAttr(group, 'TO'), 'group ' + i + ' should have TO');
|
|
1259
|
-
test.ok(hasAttr(group, 'FROM'), 'group ' + i + ' should have FROM');
|
|
1260
|
-
});
|
|
1261
|
-
|
|
1262
|
-
test.done();
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
|
-
// ============================================
|
|
1266
|
-
// Unicode CHARSET in HEADER searches
|
|
1267
|
-
// ============================================
|
|
1268
|
-
|
|
1269
|
-
module.exports['Search Compiler: Unicode header search without UTF8=ACCEPT adds CHARSET'] = test => {
|
|
1270
|
-
let connection = createMockConnection({
|
|
1271
|
-
enabled: new Set()
|
|
1272
|
-
});
|
|
1273
|
-
let compiled = searchCompiler(connection, {
|
|
1274
|
-
header: { subject: 'caf\u00e9' }
|
|
1275
|
-
});
|
|
1276
|
-
|
|
1277
|
-
test.ok(hasAttr(compiled, 'CHARSET'), 'should have CHARSET prefix');
|
|
1278
|
-
test.ok(hasAttr(compiled, 'UTF-8'), 'should have UTF-8 value');
|
|
1279
|
-
// CHARSET and UTF-8 should be the first two entries
|
|
1280
|
-
test.equal(compiled[0].value, 'CHARSET', 'CHARSET should be first');
|
|
1281
|
-
test.equal(compiled[1].value, 'UTF-8', 'UTF-8 should be second');
|
|
1282
|
-
test.ok(hasAttr(compiled, 'HEADER'), 'should contain HEADER');
|
|
1283
|
-
test.ok(hasAttr(compiled, 'caf\u00e9'), 'should contain the unicode value');
|
|
1284
|
-
test.done();
|
|
1285
|
-
};
|
|
1286
|
-
|
|
1287
|
-
module.exports['Search Compiler: Unicode header search with UTF8=ACCEPT skips CHARSET'] = test => {
|
|
1288
|
-
let connection = createMockConnection({
|
|
1289
|
-
enabled: new Set(['UTF8=ACCEPT'])
|
|
1290
|
-
});
|
|
1291
|
-
let compiled = searchCompiler(connection, {
|
|
1292
|
-
header: { subject: 'caf\u00e9' }
|
|
1293
|
-
});
|
|
1294
|
-
|
|
1295
|
-
test.ok(!hasAttr(compiled, 'CHARSET'), 'should NOT have CHARSET prefix');
|
|
1296
|
-
test.ok(!hasAttr(compiled, 'UTF-8'), 'should NOT have UTF-8 value');
|
|
1297
|
-
test.ok(hasAttr(compiled, 'HEADER'), 'should contain HEADER');
|
|
1298
|
-
test.ok(hasAttr(compiled, 'caf\u00e9'), 'should contain the unicode value');
|
|
1299
|
-
test.done();
|
|
1300
|
-
};
|