diff --git a/.gitignore b/.gitignore index 0f53da4f7..d502bdbd8 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,4 @@ newrelic_agent.log .elasticbeanstalk/* !.elasticbeanstalk/*.cfg.yml !.elasticbeanstalk/*.global.yml +/packages/local-sync/spec-saved-state.json diff --git a/packages/local-sync/package.json b/packages/local-sync/package.json index 5e866d3f2..d77e66392 100644 --- a/packages/local-sync/package.json +++ b/packages/local-sync/package.json @@ -26,6 +26,9 @@ "utf7": "^1.0.2", "vision": "4.1.0" }, + "scripts": { + "test": "../../../../node_modules/.bin/electron ../../../../ --test --spec-directory=$(pwd)/spec" + }, "repository": { "type": "git", "url": "git+https://github.com/nylas/K2.git" diff --git a/packages/local-sync/src/new-message-processor/spec/fixtures/1-99174-body.txt b/packages/local-sync/spec/fixtures/1-99174-body.txt similarity index 100% rename from packages/local-sync/src/new-message-processor/spec/fixtures/1-99174-body.txt rename to packages/local-sync/spec/fixtures/1-99174-body.txt diff --git a/packages/local-sync/src/new-message-processor/spec/fixtures/1-99174-headers.txt b/packages/local-sync/spec/fixtures/1-99174-headers.txt similarity index 100% rename from packages/local-sync/src/new-message-processor/spec/fixtures/1-99174-headers.txt rename to packages/local-sync/spec/fixtures/1-99174-headers.txt diff --git a/packages/local-sync/spec/fixtures/MessageFactory/parseFromImap/base-case.json b/packages/local-sync/spec/fixtures/MessageFactory/parseFromImap/base-case.json new file mode 100644 index 000000000..5fc82c150 --- /dev/null +++ b/packages/local-sync/spec/fixtures/MessageFactory/parseFromImap/base-case.json @@ -0,0 +1,5 @@ +{ + "imapMessage": {}, + "desiredParts": {}, + "result": {} +} diff --git a/packages/local-sync/src/new-message-processor/spec/fixtures/thread.js b/packages/local-sync/spec/fixtures/thread.js similarity index 100% rename from packages/local-sync/src/new-message-processor/spec/fixtures/thread.js rename to packages/local-sync/spec/fixtures/thread.js diff --git a/packages/local-sync/spec/local-sync-worker/fetch-folder-list-spec.js b/packages/local-sync/spec/local-sync-worker/fetch-folder-list-spec.js deleted file mode 100644 index 75d1187ba..000000000 --- a/packages/local-sync/spec/local-sync-worker/fetch-folder-list-spec.js +++ /dev/null @@ -1,115 +0,0 @@ -const {PromiseUtils} = require('isomorphic-core'); -const mockDatabase = require('./mock-database'); -const FetchFolderList = require('../../src/local-sync-worker/imap/fetch-folder-list') - -const testCategoryRoles = (db, mailboxes) => { - const mockLogger = { - info: () => {}, - debug: () => {}, - error: () => {}, - } - const mockImap = { - getBoxes: () => { - return Promise.resolve(mailboxes) - }, - } - return new FetchFolderList('fakeProvider', mockLogger).run(db, mockImap).then(() => { - const {Folder, Label} = db; - return PromiseUtils.props({ - folders: Folder.findAll(), - labels: Label.findAll(), - }).then(({folders, labels}) => { - const all = [].concat(folders, labels); - for (const category of all) { - expect(category.role).toEqual(mailboxes[category.name].role); - } - }) - }) -}; - -describe("FetchFolderList", () => { - beforeEach((done) => { - mockDatabase().then((db) => { - this.db = db; - done(); - }) - }) - - it("assigns roles when given a role attribute/flag", (done) => { - const mailboxes = { - 'Sent': {attribs: ['\\Sent'], role: 'sent'}, - 'Drafts': {attribs: ['\\Drafts'], role: 'drafts'}, - 'Spam': {attribs: ['\\Spam'], role: 'spam'}, - 'Trash': {attribs: ['\\Trash'], role: 'trash'}, - 'All Mail': {attribs: ['\\All'], role: 'all'}, - 'Important': {attribs: ['\\Important'], role: 'important'}, - 'Flagged': {attribs: ['\\Flagged'], role: 'flagged'}, - 'Inbox': {attribs: ['\\Inbox'], role: 'inbox'}, - 'TestFolder': {attribs: [], role: null}, - 'Receipts': {attribs: [], role: null}, - } - - testCategoryRoles(this.db, mailboxes).then(done, done.fail); - }) - - it("assigns missing roles by localized display names", (done) => { - const mailboxes = { - 'Sent': {attribs: [], role: 'sent'}, - 'Drafts': {attribs: ['\\Drafts'], role: 'drafts'}, - 'Spam': {attribs: ['\\Spam'], role: 'spam'}, - 'Trash': {attribs: ['\\Trash'], role: 'trash'}, - 'All Mail': {attribs: ['\\All'], role: 'all'}, - 'Important': {attribs: ['\\Important'], role: 'important'}, - 'Flagged': {attribs: ['\\Flagged'], role: 'flagged'}, - 'Inbox': {attribs: [], role: 'inbox'}, - } - - testCategoryRoles(this.db, mailboxes).then(done, done.fail); - }) - - it("doesn't assign a role more than once", (done) => { - const mailboxes = { - 'Sent': {attribs: [], role: null}, - 'Sent Items': {attribs: [], role: null}, - 'Drafts': {attribs: ['\\Drafts'], role: 'drafts'}, - 'Spam': {attribs: ['\\Spam'], role: 'spam'}, - 'Trash': {attribs: ['\\Trash'], role: 'trash'}, - 'All Mail': {attribs: ['\\All'], role: 'all'}, - 'Important': {attribs: ['\\Important'], role: 'important'}, - 'Flagged': {attribs: ['\\Flagged'], role: 'flagged'}, - 'Mail': {attribs: ['\\Inbox'], role: 'inbox'}, - 'inbox': {attribs: [], role: null}, - } - - testCategoryRoles(this.db, mailboxes).then(done, done.fail); - }) - - it("updates role assignments if an assigned category is deleted", (done) => { - let mailboxes = { - 'Sent': {attribs: [], role: null}, - 'Sent Items': {attribs: [], role: null}, - 'Drafts': {attribs: ['\\Drafts'], role: 'drafts'}, - 'Spam': {attribs: ['\\Spam'], role: 'spam'}, - 'Trash': {attribs: ['\\Trash'], role: 'trash'}, - 'All Mail': {attribs: ['\\All'], role: 'all'}, - 'Important': {attribs: ['\\Important'], role: 'important'}, - 'Flagged': {attribs: ['\\Flagged'], role: 'flagged'}, - 'Mail': {attribs: ['\\Inbox'], role: 'inbox'}, - } - - testCategoryRoles(this.db, mailboxes).then(() => { - mailboxes = { - 'Sent Items': {attribs: [], role: 'sent'}, - 'Drafts': {attribs: ['\\Drafts'], role: 'drafts'}, - 'Spam': {attribs: ['\\Spam'], role: 'spam'}, - 'Trash': {attribs: ['\\Trash'], role: 'trash'}, - 'All Mail': {attribs: ['\\All'], role: 'all'}, - 'Important': {attribs: ['\\Important'], role: 'important'}, - 'Flagged': {attribs: ['\\Flagged'], role: 'flagged'}, - 'Mail': {attribs: ['\\Inbox'], role: 'inbox'}, - } - - return testCategoryRoles(this.db, mailboxes).then(done, done.fail); - }, done.fail); - }) -}); diff --git a/packages/local-sync/spec/local-sync-worker/mock-database.js b/packages/local-sync/spec/local-sync-worker/mock-database.js deleted file mode 100644 index c2e20c47f..000000000 --- a/packages/local-sync/spec/local-sync-worker/mock-database.js +++ /dev/null @@ -1,55 +0,0 @@ -const LocalDatabaseConnector = require('../../src/shared/local-database-connector'); - -/* - * Mocks out various Model and Instance methods to prevent actually saving data - * to the sequelize database. Note that with the current implementation, only - * instances created with Model.build() are mocked out. - * - * Currently mocks out the following: - * Model - * .build() - * .findAll() - * Instance - * .destroy() - * .save() - * - */ -function mockDatabase() { - return LocalDatabaseConnector.forAccount(-1).then((db) => { - const data = {}; - for (const modelName of Object.keys(db.sequelize.models)) { - const model = db.sequelize.models[modelName]; - data[modelName] = {}; - - spyOn(model, 'findAll').and.callFake(() => { - return Promise.resolve( - Object.keys(data[modelName]).map(key => data[modelName][key]) - ); - }); - - const origBuild = model.build; - spyOn(model, 'build').and.callFake((...args) => { - const instance = origBuild.apply(model, args); - - spyOn(instance, 'save').and.callFake(() => { - if (instance.id == null) { - const sortedIds = Object.keys(data[modelName]).sort(); - const len = sortedIds.length; - instance.id = len ? +sortedIds[len - 1] + 1 : 0; - } - data[modelName][instance.id] = instance; - }); - - spyOn(instance, 'destroy').and.callFake(() => { - delete data[modelName][instance.id] - }); - - return instance; - }) - } - - return Promise.resolve(db); - }); -} - -module.exports = mockDatabase; diff --git a/packages/local-sync/spec/message-factory-spec.js b/packages/local-sync/spec/message-factory-spec.js new file mode 100644 index 000000000..c0643f26a --- /dev/null +++ b/packages/local-sync/spec/message-factory-spec.js @@ -0,0 +1,41 @@ +const path = require('path'); +const fs = require('fs'); +const LocalDatabaseConnector = require('../src/shared/local-database-connector'); +const {parseFromImap} = require('../src/shared/message-factory'); + +const FIXTURES_PATH = path.join(__dirname, 'fixtures') + +describe('MessageFactory', function MessageFactorySpecs() { + beforeEach(() => { + waitsForPromise(async () => { + const accountId = 'test-account-id'; + await LocalDatabaseConnector.ensureAccountDatabase(accountId); + const db = await LocalDatabaseConnector.forAccount(accountId); + const folder = await db.Folder.create({ + id: 'test-folder-id', + accountId: accountId, + version: 1, + name: 'Test Folder', + role: null, + }); + this.options = { accountId, db, folder }; + }) + }) + + describe("parseFromImap", () => { + const fixturesDir = path.join(FIXTURES_PATH, 'MessageFactory', 'parseFromImap'); + const filenames = fs.readdirSync(fixturesDir).filter(f => f.endsWith('.json')); + + filenames.forEach((filename) => { + it(`should correctly build message properties for ${filename}`, () => { + const inJSON = JSON.parse(fs.readFileSync(path.join(fixturesDir, filename))); + const {imapMessage, desiredParts, result} = inJSON; + + waitsForPromise(async () => { + const actual = await parseFromImap(imapMessage, desiredParts, this.options); + expect(actual).toEqual(result) + }); + }); + }) + }); +}); diff --git a/packages/local-sync/spec/threading-spec.js b/packages/local-sync/spec/threading-spec.js new file mode 100644 index 000000000..2313af432 --- /dev/null +++ b/packages/local-sync/spec/threading-spec.js @@ -0,0 +1,55 @@ +/* eslint global-require: 0 */ +/* eslint import/no-dynamic-require: 0 */ +// const path = require('path') +// const BASE_PATH = path.join(__dirname, 'fixtures') + +xdescribe('threading', function threadingSpecs() { + // it('adds the message to the thread', (done) => { + // const {message, reply} = require(`${BASE_PATH}/thread`) + // const accountId = 'a-1' + // const mockDb = { + // Thread: { + // findAll: () => { + // return Promise.resolve([{ + // id: 1, + // subject: "Loved your work and interests", + // messages: [message], + // }]) + // }, + // find: () => { + // return Promise.resolve(null) + // }, + // create: (thread) => { + // thread.id = 1 + // thread.addMessage = (newMessage) => { + // if (thread.messages) { + // thread.messages.push(newMessage.id) + // } else { + // thread.messages = [newMessage.id] + // } + // } + // return Promise.resolve(thread) + // }, + // }, + // Message: { + // findAll: () => { + // return Promise.resolve([message, reply]) + // }, + // find: () => { + // return Promise.resolve(reply) + // }, + // create: () => { + // message.setThread = (thread) => { + // message.thread = thread.id + // }; + // return Promise.resolve(message); + // }, + // }, + // } + // + // processMessage({db: mockDb, message: reply, accountId}).then((processed) => { + // expect(processed.thread).toBe(1) + // done() + // }) + // }); +}); diff --git a/packages/local-sync/src/local-sync-worker/imap/fetch-messages-in-folder.js b/packages/local-sync/src/local-sync-worker/imap/fetch-messages-in-folder.js index b5d5c30bd..f6a628cc9 100644 --- a/packages/local-sync/src/local-sync-worker/imap/fetch-messages-in-folder.js +++ b/packages/local-sync/src/local-sync-worker/imap/fetch-messages-in-folder.js @@ -239,8 +239,8 @@ class FetchMessagesInFolder { try { const messageValues = await MessageFactory.parseFromImap(imapMessage, desiredParts, { db: this._db, + folder: this._folder, accountId: this._db.accountId, - folderId: this._folder.id, }); const existingMessage = await Message.find({where: {id: messageValues.id}}); diff --git a/packages/local-sync/src/new-message-processor/spec/parsing-spec.js b/packages/local-sync/src/new-message-processor/spec/parsing-spec.js deleted file mode 100644 index e8025c0b7..000000000 --- a/packages/local-sync/src/new-message-processor/spec/parsing-spec.js +++ /dev/null @@ -1,23 +0,0 @@ -const path = require('path') -const fs = require('fs') -const {processMessage} = require('../processors/parsing') - -const BASE_PATH = path.join(__dirname, 'fixtures') - - -it('parses the message correctly', (done) => { - const bodyPath = path.join(BASE_PATH, '1-99174-body.txt') - const headersPath = path.join(BASE_PATH, '1-99174-headers.txt') - const rawBody = fs.readFileSync(bodyPath, 'utf8') - const rawHeaders = fs.readFileSync(headersPath, 'utf8') - const message = { rawHeaders, rawBody } - const bodyPart = `

In _data/apps.yml:

` - - processMessage({message}).then((processed) => { - expect(processed.headers['in-reply-to']).toEqual('') - expect(processed.headerMessageId).toEqual('') - expect(processed.subject).toEqual('Re: [electron/electron.atom.io] Add Jasper app (#352)') - expect(processed.body.includes(bodyPart)).toBe(true) - done() - }) -}) diff --git a/packages/local-sync/src/new-message-processor/spec/run.js b/packages/local-sync/src/new-message-processor/spec/run.js deleted file mode 100644 index 4eb56830f..000000000 --- a/packages/local-sync/src/new-message-processor/spec/run.js +++ /dev/null @@ -1,5 +0,0 @@ -import Jasmine from 'jasmine' - -const jasmine = new Jasmine() -jasmine.loadConfigFile('spec/support/jasmine.json') -jasmine.execute() diff --git a/packages/local-sync/src/new-message-processor/spec/support/jasmine.json b/packages/local-sync/src/new-message-processor/spec/support/jasmine.json deleted file mode 100644 index 6a2e779f4..000000000 --- a/packages/local-sync/src/new-message-processor/spec/support/jasmine.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "spec_dir": "spec", - "spec_files": [ - "**/*-spec.js" - ], - "helpers": [ ] -} diff --git a/packages/local-sync/src/new-message-processor/spec/threading-spec.js b/packages/local-sync/src/new-message-processor/spec/threading-spec.js deleted file mode 100644 index 8fbe812cf..000000000 --- a/packages/local-sync/src/new-message-processor/spec/threading-spec.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint global-require: 0 */ -/* eslint import/no-dynamic-require: 0 */ -const path = require('path') -const {processMessage} = require('../processors/threading') - -const BASE_PATH = path.join(__dirname, 'fixtures') - -it('adds the message to the thread', (done) => { - const {message, reply} = require(`${BASE_PATH}/thread`) - const accountId = 'a-1' - const mockDb = { - Thread: { - findAll: () => { - return Promise.resolve([{ - id: 1, - subject: "Loved your work and interests", - messages: [message], - }]) - }, - find: () => { - return Promise.resolve(null) - }, - create: (thread) => { - thread.id = 1 - thread.addMessage = (newMessage) => { - if (thread.messages) { - thread.messages.push(newMessage.id) - } else { - thread.messages = [newMessage.id] - } - } - return Promise.resolve(thread) - }, - }, - Message: { - findAll: () => { - return Promise.resolve([message, reply]) - }, - find: () => { - return Promise.resolve(reply) - }, - create: () => { - message.setThread = (thread) => { - message.thread = thread.id - }; - return Promise.resolve(message); - }, - }, - } - - processMessage({db: mockDb, message: reply, accountId}).then((processed) => { - expect(processed.thread).toBe(1) - done() - }) -}) diff --git a/packages/local-sync/src/shared/local-database-connector.js b/packages/local-sync/src/shared/local-database-connector.js index e6a8494f1..d1a25fd07 100644 --- a/packages/local-sync/src/shared/local-database-connector.js +++ b/packages/local-sync/src/shared/local-database-connector.js @@ -12,8 +12,9 @@ class LocalDatabaseConnector { } _sequelizePoolForDatabase(dbname) { + const storage = NylasEnv.inSpecMode() ? ':memory:' : path.join(process.env.NYLAS_HOME, `${dbname}.sqlite`); return new Sequelize(dbname, '', '', { - storage: path.join(process.env.NYLAS_HOME, `${dbname}.sqlite`), + storage: storage, dialect: "sqlite", logging: false, }) diff --git a/packages/local-sync/src/shared/message-factory.js b/packages/local-sync/src/shared/message-factory.js index 534e9466d..61375e801 100644 --- a/packages/local-sync/src/shared/message-factory.js +++ b/packages/local-sync/src/shared/message-factory.js @@ -12,8 +12,8 @@ function extractContacts(values = []) { }) } -function parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { - const {Message, Label, Folder} = db +async function parseFromImap(imapMessage, desiredParts, {db, accountId, folder}) { + const {Message, Label} = db const body = {} const {headers, attributes} = imapMessage const xGmLabels = attributes['x-gm-labels'] @@ -51,7 +51,7 @@ function parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { starred: attributes.flags.includes('\\Flagged'), date: attributes.date, folderImapUID: attributes.uid, - folderId: folderId, + folderId: folder.id, folder: null, labels: [], headers: parsedHeaders, @@ -72,40 +72,15 @@ function parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { values.snippet = values.body.substr(0, Math.min(values.body.length, SNIPPET_SIZE)); } - return Folder.findById(folderId) - .then((folder) => { - values.folder = folder + values.folder = folder + if (xGmLabels) { + values.folderImapXGMLabels = JSON.stringify(xGmLabels) + values.labels = await Label.findXGMLabels(xGmLabels) + } - if (xGmLabels) { - values.folderImapXGMLabels = JSON.stringify(xGmLabels) - return Label.findXGMLabels(xGmLabels) - .then((labels) => { - values.labels = labels - return values - }) - } - return Promise.resolve(values) - }) -} - -function buildFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { - return parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) - .then((messageValues) => db.Message.build(messageValues)) -} - -function createFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { - return parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) - .then((messageValues) => db.Message.create(messageValues)) -} - -function upsertFromImap(imapMessage, desiredParts, {db, accountId, folderId}) { - return parseFromImap(imapMessage, desiredParts, {db, accountId, folderId}) - .then((messageValues) => db.Message.upsert(messageValues)) + return values; } module.exports = { parseFromImap, - buildFromImap, - createFromImap, - upsertFromImap, }