Refactor tests (WIP)

fork
Sven Slootweg 3 years ago
parent 7344b45be0
commit 3e532e3ff6

@ -1,4 +1,4 @@
{
"extends": "@joepie91/eslint-config",
"ignorePatterns": "test/**"
"ignorePatterns": "test-old/**"
}

@ -16,7 +16,7 @@
"utf7": ">=1.0.2"
},
"scripts": {
"test": "node test/test.js"
"test": "tap --no-coverage test/tests/*"
},
"engines": {
"node": ">=0.8.0"
@ -40,6 +40,11 @@
},
"devDependencies": {
"@joepie91/eslint-config": "^1.1.0",
"eslint": "^7.20.0"
"bl": "^5.0.0",
"chalk": "^4.1.1",
"eslint": "^7.20.0",
"p-event": "^4.2.0",
"p-stream": "^1.0.3",
"tap": "^15.0.9"
}
}

@ -91,18 +91,18 @@ srv.listen(0, '127.0.0.1', function() {
imap.on('ready', function() {
srv.close();
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch([1,2], { bodies: ['TEXT'] });
var nbody = -1;
f.on('message', function(m) {
var fetchOp = imap.seq.fetch([1,2], { bodies: ['TEXT'] });
var bodyCounter = -1;
fetchOp.on('message', function(m) {
m.on('body', function(stream, info) {
++nbody;
++bodyCounter;
bodyInfo.push(info);
body[nbody] = '';
if (nbody === 0) {
body[bodyCounter] = '';
if (bodyCounter === 0) {
// first allow body.push() to return false in parser.js
setTimeout(function() {
stream.on('data', function(chunk) {
body[nbody] += chunk.toString('binary');
body[bodyCounter] += chunk.toString('binary');
});
setTimeout(function() {
var oldRead = stream._read,
@ -137,7 +137,7 @@ srv.listen(0, '127.0.0.1', function() {
}, 100);
} else {
stream.on('data', function(chunk) {
body[nbody] += chunk.toString('binary');
body[bodyCounter] += chunk.toString('binary');
});
}
});
@ -145,7 +145,7 @@ srv.listen(0, '127.0.0.1', function() {
result.push(attrs);
});
});
f.on('end', function() {
fetchOp.on('end', function() {
imap.end();
});
});
@ -173,4 +173,4 @@ process.once('exit', function() {
which: 'TEXT',
size: 200
}]);
});
});

@ -0,0 +1,45 @@
"use strict";
const Promise = require("bluebird");
const pEvent = require("p-event");
const pStream = require("p-stream");
module.exports = function fetch(client, args, options = {}) {
return new Promise((resolve, reject) => {
let attributeResults = [];
let bodyInfoResults = [];
let bodyPromises = [];
let fetchOp = client.seq.fetch(... args);
fetchOp.on("end", () => {
resolve(Promise.try(() => {
if (options.onEnd != null) {
return options.onEnd();
}
}).then(() => {
return Promise.all(bodyPromises);
}).then((bodies) => {
return {
attributeResults,
bodyInfoResults,
bodies
};
}));
});
fetchOp.on("message", (message) => {
message.on("attributes", (attributes) => {
attributeResults.push(attributes);
});
message.on("body", (bodyStream, info) => {
bodyInfoResults.push(info);
bodyPromises.push(pStream(bodyStream));
});
});
pEvent(fetchOp, "error").then(reject);
});
};

@ -0,0 +1,9 @@
"use strict";
module.exports = function concatenateLines(lines, includeTrailingNewline = true) {
let allLines = (includeTrailingNewline)
? lines.concat([ "" ])
: lines;
return allLines.filter((line) => line != null).join("\r\n");
};

@ -0,0 +1,66 @@
"use strict";
const net = require("net");
const Promise = require("bluebird");
const chalk = require("chalk");
const CRLF = "\r\n";
module.exports = function createMockServer({ steps, test, handle = ()=>{} }) {
let counter = 0;
const server = net.createServer((socket) => {
let buffer = "";
socket.write("* OK asdf\r\n");
socket.on("data", (data) => {
buffer += data.toString("utf8");
if (buffer.includes(CRLF)) {
let lines = buffer.split(CRLF);
buffer = lines.pop();
Promise.map(lines, (line) => {
return Promise.try(() => {
console.log(chalk.gray(line));
return handle(line);
}).then((result) => {
if (result == null) {
let step = steps[counter];
counter += 1;
if (step.expected != null) {
test.same(line, step.expected);
}
return (typeof step.response === "function")
? step.response()
: step.response;
} else {
return result;
}
}).then((response) => {
socket.write(response);
});
}, { concurrency: 1 });
}
});
});
return new Promise((resolve, reject) => {
server.listen(0, "127.0.0.1", () => {
// FIXME: Error condition?
resolve({
server: server,
port: server.address().port,
finalize: function () {
if (counter < steps.length) {
test.ok(false, `Mock server expected ${steps.length} steps but only saw ${counter}`);
}
}
});
});
});
};

@ -0,0 +1,150 @@
"use strict";
const Promise = require("bluebird");
const tap = require("tap");
const pEvent = require("p-event");
const IMAP = require("../..");
const lines = require("../lines");
const createMockServer = require("../mock-server");
const fetch = require("../fetch");
tap.test("fetch-with-uid", (test) => {
return testFetch(test, true, false);
});
tap.test("fetch-without-uid", (test) => {
return testFetch(test, false, false);
});
tap.test("fetch-with-body", (test) => {
return testFetch(test, true, true);
});
function testFetch(test, withUID, withBody) {
let steps = [{
expected: 'A0 CAPABILITY',
response: lines([
'* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
]),
}, {
expected: 'A1 LOGIN "foo" "bar"',
response: lines([
'* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
]),
}, {
expected: 'A2 NAMESPACE',
response: lines([
'* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
]),
}, {
expected: 'A3 LIST "" ""',
response: lines([
'* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
]),
}, {
expected: 'A4 EXAMINE "INBOX"',
response: lines([
'* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
]),
}, {
expected: (withBody)
? 'A5 FETCH 1 (UID FLAGS INTERNALDATE BODY.PEEK[TEXT])'
: 'A5 FETCH 1 (UID FLAGS INTERNALDATE)',
response: lines([
'* 1 FETCH (UID 1)',
`* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000"${(withUID) ? " UID 1000" : ""})`,
(withBody)
? '* 1 FETCH (BODY[TEXT] "IMAP is terrible")'
: null,
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
]),
}, {
expected: "A6 LOGOUT",
response: lines([
'* BYE LOGOUT Requested',
'A6 OK good day (Success)',
]),
}];
return Promise.try(() => {
let continuationWasSent;
return createMockServer({
steps: steps,
test: test,
// NOTE: Anything handled here is not matched against steps, nor is the counter incremented
handle: function (line) {
if (line === "IDLE IDLE") {
continuationWasSent = true;
return Promise.delay(100).then(() => {
return lines([ "+ idling" ]);
});
} else if (line === "DONE") {
test.ok(continuationWasSent, "DONE seen before continuation sent");
continuationWasSent = false;
return lines([ "IDLE ok" ]);
}
}
});
}).then(({ server, port, finalize }) => {
const client = new IMAP({
user: "foo",
password: "bar",
host: "127.0.0.1",
port: port,
keepalive: false
});
Promise.promisifyAll(client, { multiArgs: true });
client.connect();
return Promise.try(() => {
return pEvent(client, "ready");
}).then(() => {
server.close(); // Stop listening for new clients
return client.openBoxAsync("INBOX", true);
}).then(() => {
return fetch( client,
(withBody)
? [ 1, { bodies: [ "TEXT" ] } ]
: [ 1 ]
);
}).tap(() => {
client.end();
return pEvent(client, "end");
}).then(({ attributeResults, bodyInfoResults, bodies }) => {
finalize();
test.same(attributeResults, [{
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
}]);
if (withBody) {
test.same(bodies, [ Buffer.from('IMAP is terrible') ]);
test.same(bodyInfoResults, [{
seqno: 1,
which: 'TEXT',
size: 16
}]);
}
});
});
}

@ -0,0 +1,154 @@
"use strict";
const Promise = require("bluebird");
const tap = require("tap");
const pEvent = require("p-event");
const IMAP = require("../..");
const lines = require("../lines");
const createMockServer = require("../mock-server");
const fetch = require("../fetch");
tap.test("idle-with-delay", (test) => {
return testIdle(test, true);
});
tap.test("idle-without-delay", (test) => {
return testIdle(test, false);
});
function testIdle(test, withDelay) {
let steps = [{
expected: 'A0 CAPABILITY',
response: lines([
'* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
]),
}, {
expected: 'A1 LOGIN "foo" "bar"',
response: lines([
'* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
]),
}, {
expected: 'A2 NAMESPACE',
response: lines([
'* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
]),
}, {
expected: 'A3 LIST "" ""',
response: lines([
'* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
]),
}, {
expected: 'A4 EXAMINE "INBOX"',
response: lines([
'* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)'
]),
}, {
expected: 'A5 FETCH 1 (UID FLAGS INTERNALDATE BODY.PEEK[TEXT])',
response: lines([
'* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000" UID 1000)',
'* 1 FETCH (BODY[TEXT] "IMAP is terrible")',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
]),
}, {
expected: 'A6 STATUS "test" (MESSAGES RECENT UNSEEN UIDVALIDITY UIDNEXT)',
response: lines([
'* STATUS test (MESSAGES 231 RECENT 0 UNSEEN 0 UIDVALIDITY 123 UIDNEXT 442)',
'A6 OK STATUS completed',
]),
}, {
expected: 'A7 LOGOUT',
response: lines([
'* BYE LOGOUT Requested',
'A7 OK good day (Success)',
]),
}];
return Promise.try(() => {
let continuationWasSent;
return createMockServer({
steps: steps,
test: test,
// NOTE: Anything handled here is not matched against steps, nor is the counter incremented
handle: function (line) {
if (line === "IDLE IDLE") {
continuationWasSent = true;
return Promise.delay(100).then(() => {
return lines([ "+ idling" ]);
});
} else if (line === "DONE") {
test.ok(continuationWasSent, "DONE seen before continuation sent");
continuationWasSent = false;
return lines([ "IDLE ok" ]);
}
}
});
}).then(({ server, port, finalize }) => {
const client = new IMAP({
user: "foo",
password: "bar",
host: "127.0.0.1",
port: port,
keepalive: true // NOTE: Different in other tests!
});
Promise.promisifyAll(client, { multiArgs: true });
client.connect();
return Promise.try(() => {
return pEvent(client, "ready");
}).then(() => {
server.close(); // Stop listening for new clients
return client.openBoxAsync("INBOX", true);
}).then(() => {
return fetch( client,
[ 1, { bodies: [ "TEXT" ] } ]
);
}).tap(() => {
return Promise.try(() => {
if (withDelay) {
return Promise.delay(500);
}
}).then(() => {
return Promise.try(() => {
return client.statusAsync("test");
}).then((_status) => {
client.end();
return pEvent(client, "end");
}).timeout(500, "Timed out waiting for STATUS");
});
}).then(({ attributeResults, bodyInfoResults, bodies }) => {
finalize();
test.same(attributeResults, [{
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
}]);
test.same(bodies, [ Buffer.from('IMAP is terrible') ]);
test.same(bodyInfoResults, [{
seqno: 1,
which: 'TEXT',
size: 16
}]);
});
});
}

@ -0,0 +1,245 @@
"use strict";
const Promise = require("bluebird");
const pEvent = require("p-event");
const tap = require("tap");
const BL = require("bl");
const { Parser } = require("../../lib/Parser");
tap.test("parse-session", (test) => {
let session = `
Server: * PREAUTH [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE SORT SORT=DISPLAY THREAD=REFERENCES THREAD=REFS THREAD=ORDEREDSUBJECT MULTIAPPEND URL-PARTIAL CATENATE UNSELECT CHILDREN NAMESPACE UIDPLUS LIST-EXTENDED I18NLEVEL=1 CONDSTORE QRESYNC ESEARCH ESORT SEARCHRES WITHIN CONTEXT=SEARCH LIST-STATUS SPECIAL-USE BINARY MOVE ACL RIGHTS=texk] Logged in as m.markov at domain.com
Client: 1 LIST "" "*"
Server: * LIST (\\HasNoChildren) "/" confirmed-spam
Server: * LIST (\\HasNoChildren \\Trash) "/" Trash
Server: * LIST (\\HasNoChildren) "/" SpamLikely
Server: * LIST (\\HasNoChildren) "/" Spam
Server: * LIST (\\HasNoChildren) "/" "Sent Items"
Server: * LIST (\\HasNoChildren) "/" Archive
Server: * LIST (\\HasNoChildren \\Drafts) "/" Drafts
Server: * LIST (\\HasNoChildren) "/" Notes
Server: * LIST (\\HasNoChildren) "/" TeamViewer
Server: * LIST (\\HasNoChildren \\Sent) "/" "Sent Messages"
Server: * LIST (\\HasNoChildren) "/" confirmed-ham
Server: * LIST (\\Noselect \\HasChildren) "/" Public
Server: * LIST (\\HasNoChildren) "/" Public/office3
Server: * LIST (\\HasNoChildren) "/" Public/office4
Server: * LIST (\\HasNoChildren) "/" Public/support
Server: * LIST (\\HasNoChildren) "/" Public/root
Server: * LIST (\\HasNoChildren) "/" Public/updates
Server: * LIST (\\HasNoChildren) "/" Public/postmaster
Server: * LIST (\\Noselect \\HasChildren) "/" Shared
Server: * LIST (\\Noselect \\HasChildren) "/" Shared/d.marteva
Server: * LIST (\\HasNoChildren) "/" Shared/d.marteva/INBOX
Server: * LIST (\\HasNoChildren) "/" INBOX
Server: 1 OK List completed.
Client: 2 LOGOUT
`.replace(/\n/g, "\r\n").replace(/^\t+/gm, "");
let serverSession = session
.split("\n")
.filter((line) => line.startsWith("Server:"))
.map((line) => line.replace(/^Server: /, ""))
.join("\n");
let serverBuffer = new BL([ Buffer.from(serverSession) ]);
let expectedResults = [{
"type": "preauth",
"textCode": {
"key": "CAPABILITY",
"val": ["IMAP4rev1", "LITERAL+", "SASL-IR", "LOGIN-REFERRALS", "ID", "ENABLE", "IDLE", "SORT", "SORT=DISPLAY", "THREAD=REFERENCES", "THREAD=REFS", "THREAD=ORDEREDSUBJECT", "MULTIAPPEND", "URL-PARTIAL", "CATENATE", "UNSELECT", "CHILDREN", "NAMESPACE", "UIDPLUS", "LIST-EXTENDED", "I18NLEVEL=1", "CONDSTORE", "QRESYNC", "ESEARCH", "ESORT", "SEARCHRES", "WITHIN", "CONTEXT=SEARCH", "LIST-STATUS", "SPECIAL-USE", "BINARY", "MOVE", "ACL", "RIGHTS=texk"]
},
"text": "Logged in as m.markov at domain.com"
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "confirmed-spam"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren", "\\Trash"],
"delimiter": "/",
"name": "Trash"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "SpamLikely"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Spam"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Sent Items"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Archive"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren", "\\Drafts"],
"delimiter": "/",
"name": "Drafts"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Notes"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "TeamViewer"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren", "\\Sent"],
"delimiter": "/",
"name": "Sent Messages"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "confirmed-ham"
}
}, {
"type": "list",
"text": {
"flags": ["\\Noselect", "\\HasChildren"],
"delimiter": "/",
"name": "Public"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/office3"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/office4"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/support"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/root"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/updates"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Public/postmaster"
}
}, {
"type": "list",
"text": {
"flags": ["\\Noselect", "\\HasChildren"],
"delimiter": "/",
"name": "Shared"
}
}, {
"type": "list",
"text": {
"flags": ["\\Noselect", "\\HasChildren"],
"delimiter": "/",
"name": "Shared/d.marteva"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "Shared/d.marteva/INBOX"
}
}, {
"type": "list",
"text": {
"flags": ["\\HasNoChildren"],
"delimiter": "/",
"name": "INBOX"
}
}];
let stream = new BL.BufferListStream(serverBuffer);
let parser = new Parser(stream);
let results = [];
parser.on("tagged", (item) => {
// console.log("tagged", item);
results.push(item);
});
parser.on("untagged", (item) => {
// console.log("untagged", item);
results.push(item);
});
parser.on("continue", (... args) => {
// console.log("continue", args);
});
parser.on("other", (... args) => {
// console.log("other", args);
});
parser.on("body", (... args) => {
// console.log("body", args);
});
return Promise.try(() => {
// NOTE: Parser does not expose an 'end' event
return pEvent(stream, "end");
}).then(() => {
// Hack for clearing out `undefined` properties
let normalizedResults = JSON.parse(JSON.stringify(results));
test.same(normalizedResults, expectedResults);
});
});

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save