864 lines
26 KiB
JavaScript
864 lines
26 KiB
JavaScript
'use strict';
|
|
|
|
const { searchCompiler } = require('../lib/search-compiler');
|
|
|
|
// Mock mailbox for testing
|
|
let createMockMailbox = () => ({
|
|
flags: new Set(['\\Seen', '\\Answered', '\\Flagged', '\\Deleted', '\\Draft', '$CustomFlag']),
|
|
permanentFlags: new Set(['\\*'])
|
|
});
|
|
|
|
// Helper to create mock connection with customizable capabilities
|
|
let createMockConnection = (options = {}) => ({
|
|
capabilities: new Map(options.capabilities || [['IMAP4rev1', true]]),
|
|
enabled: new Set(options.enabled || []),
|
|
mailbox: options.mailbox || createMockMailbox()
|
|
});
|
|
|
|
// Helper to find attribute by value
|
|
let findAttr = (attrs, value) => attrs.find(a => a.value === value);
|
|
let hasAttr = (attrs, value) => attrs.some(a => a.value === value);
|
|
|
|
// ============================================
|
|
// Basic functionality tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: Basic functionality'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
test.doesNotThrow(() => {
|
|
let compiled = searchCompiler(connection, { seen: false });
|
|
test.ok(Array.isArray(compiled));
|
|
});
|
|
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Empty query'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {});
|
|
|
|
test.ok(Array.isArray(compiled));
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Null/undefined query'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
let compiled1 = searchCompiler(connection, null);
|
|
test.ok(Array.isArray(compiled1));
|
|
|
|
let compiled2 = searchCompiler(connection, undefined);
|
|
test.ok(Array.isArray(compiled2));
|
|
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// SEQ (sequence) tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: SEQ with string'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { seq: '1:100' });
|
|
|
|
test.ok(hasAttr(compiled, '1:100'));
|
|
let seqAttr = findAttr(compiled, '1:100');
|
|
test.equal(seqAttr.type, 'SEQUENCE');
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SEQ with number'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { seq: 42 });
|
|
|
|
test.ok(hasAttr(compiled, '42'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SEQ ignores invalid values'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
// Whitespace in sequence is invalid
|
|
let compiled = searchCompiler(connection, { seq: '1 2 3' });
|
|
test.equal(compiled.length, 0);
|
|
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Boolean flag tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: SEEN flag true'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { seen: true });
|
|
|
|
test.ok(hasAttr(compiled, 'SEEN'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SEEN flag false adds UNSEEN'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { seen: false });
|
|
|
|
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: UNSEEN flag false adds SEEN'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { unseen: false });
|
|
|
|
test.ok(hasAttr(compiled, 'SEEN'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: All boolean flags'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
// Test all toggleable flags
|
|
let flags = ['answered', 'deleted', 'draft', 'flagged', 'seen'];
|
|
flags.forEach(flag => {
|
|
let compiled = searchCompiler(connection, { [flag]: true });
|
|
test.ok(hasAttr(compiled, flag.toUpperCase()), `${flag} should be present`);
|
|
});
|
|
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: UN-prefixed flags'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
let compiled = searchCompiler(connection, {
|
|
unanswered: true,
|
|
undeleted: true,
|
|
undraft: true,
|
|
unflagged: true
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'UNANSWERED'));
|
|
test.ok(hasAttr(compiled, 'UNDELETED'));
|
|
test.ok(hasAttr(compiled, 'UNDRAFT'));
|
|
test.ok(hasAttr(compiled, 'UNFLAGGED'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Simple boolean flags (ALL, NEW, OLD, RECENT)
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: ALL flag'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { all: true });
|
|
|
|
test.ok(hasAttr(compiled, 'ALL'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: NEW flag'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { new: true });
|
|
|
|
test.ok(hasAttr(compiled, 'NEW'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OLD flag'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { old: true });
|
|
|
|
test.ok(hasAttr(compiled, 'OLD'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: RECENT flag'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { recent: true });
|
|
|
|
test.ok(hasAttr(compiled, 'RECENT'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Simple flags ignored when falsy'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
all: false,
|
|
new: false,
|
|
old: false,
|
|
recent: false
|
|
});
|
|
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Numeric comparison tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: LARGER'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { larger: 10000 });
|
|
|
|
test.ok(hasAttr(compiled, 'LARGER'));
|
|
test.ok(hasAttr(compiled, '10000'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SMALLER'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { smaller: 5000 });
|
|
|
|
test.ok(hasAttr(compiled, 'SMALLER'));
|
|
test.ok(hasAttr(compiled, '5000'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: MODSEQ'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { modseq: 123456 });
|
|
|
|
test.ok(hasAttr(compiled, 'MODSEQ'));
|
|
test.ok(hasAttr(compiled, '123456'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Numeric ignores falsy values'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
larger: 0,
|
|
smaller: null,
|
|
modseq: undefined
|
|
});
|
|
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Text search tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: FROM'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.ok(hasAttr(compiled, 'user@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: TO'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { to: 'recipient@example.com' });
|
|
|
|
test.ok(hasAttr(compiled, 'TO'));
|
|
test.ok(hasAttr(compiled, 'recipient@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: CC'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { cc: 'cc@example.com' });
|
|
|
|
test.ok(hasAttr(compiled, 'CC'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: BCC'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { bcc: 'bcc@example.com' });
|
|
|
|
test.ok(hasAttr(compiled, 'BCC'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SUBJECT'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { subject: 'Test Subject' });
|
|
|
|
test.ok(hasAttr(compiled, 'SUBJECT'));
|
|
test.ok(hasAttr(compiled, 'Test Subject'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: BODY'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { body: 'search text' });
|
|
|
|
test.ok(hasAttr(compiled, 'BODY'));
|
|
test.ok(hasAttr(compiled, 'search text'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: TEXT'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { text: 'full text search' });
|
|
|
|
test.ok(hasAttr(compiled, 'TEXT'));
|
|
test.ok(hasAttr(compiled, 'full text search'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Text fields ignore falsy'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
from: '',
|
|
to: null,
|
|
subject: undefined
|
|
});
|
|
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// UID tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: UID with string'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { uid: '1:*' });
|
|
|
|
test.ok(hasAttr(compiled, 'UID'));
|
|
let uidValueAttr = compiled.find(a => a.value === '1:*');
|
|
test.equal(uidValueAttr.type, 'SEQUENCE');
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: UID with number'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { uid: 12345 });
|
|
|
|
test.ok(hasAttr(compiled, 'UID'));
|
|
test.ok(hasAttr(compiled, '12345'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// EMAILID / THREADID tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: EMAILID with OBJECTID'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['OBJECTID', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { emailId: 'M1234567890' });
|
|
|
|
test.ok(hasAttr(compiled, 'EMAILID'));
|
|
test.ok(hasAttr(compiled, 'M1234567890'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: EMAILID falls back to X-GM-MSGID'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['X-GM-EXT-1', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { emailId: '1234567890' });
|
|
|
|
test.ok(hasAttr(compiled, 'X-GM-MSGID'));
|
|
test.ok(hasAttr(compiled, '1234567890'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: EMAILID ignored without capability'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { emailId: '12345' });
|
|
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: THREADID with OBJECTID'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['OBJECTID', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { threadId: 'T1234567890' });
|
|
|
|
test.ok(hasAttr(compiled, 'THREADID'));
|
|
test.ok(hasAttr(compiled, 'T1234567890'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: THREADID falls back to X-GM-THRID'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['X-GM-EXT-1', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { threadId: '9876543210' });
|
|
|
|
test.ok(hasAttr(compiled, 'X-GM-THRID'));
|
|
test.ok(hasAttr(compiled, '9876543210'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Gmail raw search tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: GMRAW with X-GM-EXT-1'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['X-GM-EXT-1', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { gmraw: 'in:inbox is:unread' });
|
|
|
|
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
test.ok(hasAttr(compiled, 'in:inbox is:unread'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: GMAILRAW alias'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['X-GM-EXT-1', true]]
|
|
});
|
|
let compiled = searchCompiler(connection, { gmailraw: 'has:attachment' });
|
|
|
|
test.ok(hasAttr(compiled, 'X-GM-RAW'));
|
|
test.ok(hasAttr(compiled, 'has:attachment'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: GMRAW throws without capability'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
try {
|
|
searchCompiler(connection, { gmraw: 'test' });
|
|
test.ok(false, 'Should have thrown');
|
|
} catch (err) {
|
|
test.equal(err.code, 'MissingServerExtension');
|
|
test.ok(err.message.includes('X-GM-EXT-1'));
|
|
}
|
|
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Date search tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: SINCE'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { since: new Date('2023-06-15') });
|
|
|
|
test.ok(hasAttr(compiled, 'SINCE'));
|
|
test.ok(hasAttr(compiled, '15-Jun-2023'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: BEFORE'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T00:00:00.000Z') });
|
|
|
|
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: BEFORE with non-midnight time adjusts date'] = test => {
|
|
let connection = createMockConnection();
|
|
// Non-midnight time should advance to next day
|
|
let compiled = searchCompiler(connection, { before: new Date('2023-06-15T12:30:00.000Z') });
|
|
|
|
test.ok(hasAttr(compiled, 'BEFORE'));
|
|
// Should be 16-Jun-2023 (next day)
|
|
test.ok(hasAttr(compiled, '16-Jun-2023'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: ON'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { on: new Date('2023-06-15') });
|
|
|
|
test.ok(hasAttr(compiled, 'ON'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SENTBEFORE'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { sentbefore: new Date('2023-06-15T00:00:00.000Z') });
|
|
|
|
test.ok(hasAttr(compiled, 'SENTBEFORE'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SENTON'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { senton: new Date('2023-06-15') });
|
|
|
|
test.ok(hasAttr(compiled, 'SENTON'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SENTSINCE'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { sentsince: new Date('2023-06-15') });
|
|
|
|
test.ok(hasAttr(compiled, 'SENTSINCE'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: SINCE with WITHIN extension'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['WITHIN', true]]
|
|
});
|
|
let recentDate = new Date(Date.now() - 3600 * 1000); // 1 hour ago
|
|
let compiled = searchCompiler(connection, { since: recentDate });
|
|
|
|
test.ok(hasAttr(compiled, 'YOUNGER'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: BEFORE with WITHIN extension'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['WITHIN', true]]
|
|
});
|
|
let oldDate = new Date(Date.now() - 86400 * 1000); // 1 day ago
|
|
let compiled = searchCompiler(connection, { before: oldDate });
|
|
|
|
test.ok(hasAttr(compiled, 'OLDER'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Date with invalid value ignored'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { since: 'invalid-date' });
|
|
|
|
// formatDate returns undefined for invalid dates
|
|
test.equal(compiled.length, 0);
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// KEYWORD tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: KEYWORD'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { keyword: '$CustomFlag' });
|
|
|
|
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
test.ok(hasAttr(compiled, '$CustomFlag'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: UNKEYWORD'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { unkeyword: '$CustomFlag' });
|
|
|
|
test.ok(hasAttr(compiled, 'UNKEYWORD'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: KEYWORD with standard flag'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, { keyword: '\\Seen' });
|
|
|
|
test.ok(hasAttr(compiled, 'KEYWORD'));
|
|
test.ok(hasAttr(compiled, '\\Seen'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// HEADER tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: HEADER with value'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
header: {
|
|
'X-Custom-Header': 'custom-value'
|
|
}
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'HEADER'));
|
|
test.ok(hasAttr(compiled, 'X-CUSTOM-HEADER'));
|
|
test.ok(hasAttr(compiled, 'custom-value'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: HEADER existence check'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
header: {
|
|
'X-Priority': true // Check header exists
|
|
}
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'HEADER'));
|
|
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
test.ok(hasAttr(compiled, '')); // Empty string for existence check
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: HEADER multiple headers'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
header: {
|
|
'X-Mailer': 'Outlook',
|
|
'X-Priority': '1'
|
|
}
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'X-MAILER'));
|
|
test.ok(hasAttr(compiled, 'X-PRIORITY'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: HEADER ignores non-string values'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
header: {
|
|
'X-Number': 123,
|
|
'X-Null': null
|
|
}
|
|
});
|
|
|
|
// Non-string values (except true) should be skipped
|
|
test.ok(!hasAttr(compiled, 'X-NUMBER'));
|
|
test.ok(!hasAttr(compiled, 'X-NULL'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: HEADER with null/invalid object'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
let compiled1 = searchCompiler(connection, { header: null });
|
|
test.equal(compiled1.length, 0);
|
|
|
|
let compiled2 = searchCompiler(connection, { header: 'not-an-object' });
|
|
test.equal(compiled2.length, 0);
|
|
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// NOT operator tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: NOT operator'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
not: { from: 'spam@example.com' }
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'NOT'));
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.ok(hasAttr(compiled, 'spam@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: NOT with nested conditions'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
not: {
|
|
seen: true,
|
|
from: 'test@example.com'
|
|
}
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'NOT'));
|
|
test.ok(hasAttr(compiled, 'SEEN'));
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: NOT ignored when falsy'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
let compiled1 = searchCompiler(connection, { not: null });
|
|
test.equal(compiled1.length, 0);
|
|
|
|
let compiled2 = searchCompiler(connection, { not: false });
|
|
test.equal(compiled2.length, 0);
|
|
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// OR operator tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: OR with two conditions'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
or: [{ from: 'alice@example.com' }, { from: 'bob@example.com' }]
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'OR'));
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.ok(hasAttr(compiled, 'alice@example.com'));
|
|
test.ok(hasAttr(compiled, 'bob@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR with single condition'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
or: [{ from: 'only@example.com' }]
|
|
});
|
|
|
|
// Single condition should not add OR
|
|
test.ok(!hasAttr(compiled, 'OR'));
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.ok(hasAttr(compiled, 'only@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR with three conditions'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }]
|
|
});
|
|
|
|
// Should have OR for tree structure
|
|
test.ok(hasAttr(compiled, 'OR'));
|
|
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
test.ok(hasAttr(compiled, 'b@example.com'));
|
|
test.ok(hasAttr(compiled, 'c@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR with four conditions'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }, { from: 'c@example.com' }, { from: 'd@example.com' }]
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'OR'));
|
|
test.ok(hasAttr(compiled, 'a@example.com'));
|
|
test.ok(hasAttr(compiled, 'd@example.com'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR ignored when empty'] = test => {
|
|
let connection = createMockConnection();
|
|
|
|
let compiled1 = searchCompiler(connection, { or: [] });
|
|
test.equal(compiled1.length, 0);
|
|
|
|
let compiled2 = searchCompiler(connection, { or: null });
|
|
test.equal(compiled2.length, 0);
|
|
|
|
let compiled3 = searchCompiler(connection, { or: 'not-an-array' });
|
|
test.equal(compiled3.length, 0);
|
|
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR with null entry in array'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
or: [{ from: 'test@example.com' }, null]
|
|
});
|
|
|
|
// Should still process valid entries
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Unicode / CHARSET tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: Unicode adds CHARSET UTF-8'] = test => {
|
|
let connection = createMockConnection({
|
|
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
});
|
|
let compiled = searchCompiler(connection, { from: 'test@example.com' });
|
|
|
|
// No unicode, no charset
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
|
|
// With unicode
|
|
let compiled2 = searchCompiler(connection, { subject: 'Test' });
|
|
test.ok(!hasAttr(compiled2, 'CHARSET'));
|
|
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Unicode in subject adds CHARSET'] = test => {
|
|
let connection = createMockConnection({
|
|
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
});
|
|
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Unicode text triggers CHARSET'] = test => {
|
|
let connection = createMockConnection({
|
|
enabled: new Set() // UTF8=ACCEPT not enabled
|
|
});
|
|
let compiled = searchCompiler(connection, { from: 'user@example.com' });
|
|
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: Unicode skipped when UTF8=ACCEPT enabled'] = test => {
|
|
let connection = createMockConnection({
|
|
enabled: new Set(['UTF8=ACCEPT'])
|
|
});
|
|
let compiled = searchCompiler(connection, { subject: 'Test' });
|
|
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: GMRAW with Unicode adds CHARSET'] = test => {
|
|
let connection = createMockConnection({
|
|
capabilities: [['X-GM-EXT-1', true]],
|
|
enabled: new Set()
|
|
});
|
|
let compiled = searchCompiler(connection, { gmraw: 'test query' });
|
|
|
|
// ASCII query, no charset needed
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: HEADER with Unicode adds CHARSET'] = test => {
|
|
let connection = createMockConnection({
|
|
enabled: new Set()
|
|
});
|
|
let compiled = searchCompiler(connection, {
|
|
header: { Subject: 'ASCII only' }
|
|
});
|
|
|
|
test.ok(!hasAttr(compiled, 'CHARSET'));
|
|
test.done();
|
|
};
|
|
|
|
// ============================================
|
|
// Complex query tests
|
|
// ============================================
|
|
|
|
module.exports['Search Compiler: Complex combined query'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
seen: false,
|
|
from: 'sender@example.com',
|
|
since: new Date('2023-01-01'),
|
|
larger: 1000
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'UNSEEN'));
|
|
test.ok(hasAttr(compiled, 'FROM'));
|
|
test.ok(hasAttr(compiled, 'SINCE'));
|
|
test.ok(hasAttr(compiled, 'LARGER'));
|
|
test.done();
|
|
};
|
|
|
|
module.exports['Search Compiler: OR combined with other criteria'] = test => {
|
|
let connection = createMockConnection();
|
|
let compiled = searchCompiler(connection, {
|
|
seen: true,
|
|
or: [{ from: 'a@example.com' }, { from: 'b@example.com' }]
|
|
});
|
|
|
|
test.ok(hasAttr(compiled, 'SEEN'));
|
|
test.ok(hasAttr(compiled, 'OR'));
|
|
test.done();
|
|
};
|