diff --git a/__mocks__/fs.js b/__mocks__/fs.js index 9acb55c..832f388 100644 --- a/__mocks__/fs.js +++ b/__mocks__/fs.js @@ -9,6 +9,7 @@ let disk = {}; * @param data */ function writePath(parentDir, path, data) { + path = path.replace(/\/+/g, '/').replace(/\/+$/, ''); let files = path.split('/'); let file = files.shift(); @@ -35,7 +36,7 @@ function readPath(parentDir, path) { let files = path.split('/'); let file = files.shift(); - if(!parentDir[file]) throw new Error('File does not exist'); + if(!parentDir[file]) throw new Error('File does not exist: ' + path); if(files.length > 0) { return readPath(parentDir[file].tree, files.join('/')); @@ -44,6 +45,40 @@ function readPath(parentDir, path) { } } +function unlink(parentDir, path) { + path = path.replace(/\/+/g, '/').replace(/\/+$/, ''); + let files = path.split('/'); + let file = files.shift(); + + if(!parentDir[file]) return; + + if(files.length > 0) { + return unlink(parentDir[file].tree, files.join('/')); + } else { + delete parentDir[file]; + } +} + +function stat(path, callback) { + try { + // check that path exists + readPath(disk, path); + let stats = { + isFile: jest.fn(function() { + let content = readPath(disk, path).content; + return content !== null && content !== undefined; + }), + isDirectory: jest.fn(function() { + let content = readPath(disk, path).content; + return content === null || content === undefined; + }) + }; + callback(null, stats); + } catch(err) { + callback(err, null); + } +} + module.exports = { writeFileSync: jest.fn(writeFileSync), writeFile: jest.fn(function(path, contents, callback) { @@ -77,5 +112,31 @@ module.exports = { return contents === null || contents === undefined; }) }; + }), + stat: jest.fn(stat), + lstat: jest.fn(stat), + readdir: jest.fn(function(path, options, callback) { + if(typeof options === 'function') { + callback = options; + options = {}; + } + try { + let file = readPath(disk, path); + callback(null, Object.keys(file.tree)); + } catch(err) { + callback(err, []); + } + }), + unlink: jest.fn(function(path, callback) { + unlink(disk, path); + callback(); + }), + __deepCopy: jest.fn(function(path, dest) { + let inode = readPath(disk, path); + // create dummy file + writeFileSync(dest, ''); + let destInode = readPath(disk, dest); + destInode.content = undefined; + destInode.tree = inode.tree; }) }; \ No newline at end of file diff --git a/__mocks__/ncp.js b/__mocks__/ncp.js new file mode 100644 index 0000000..e415780 --- /dev/null +++ b/__mocks__/ncp.js @@ -0,0 +1,11 @@ +'use strict'; +let fs = require('fs'); + +module.exports.ncp = jest.fn(function(path, dest, callback) { + try { + fs.__deepCopy(path, dest); + callback(); + } catch (err) { + callback(err); + } +}); \ No newline at end of file diff --git a/__tests__/library-tests.js b/__tests__/library-tests.js index b410518..b297f86 100644 --- a/__tests__/library-tests.js +++ b/__tests__/library-tests.js @@ -697,24 +697,26 @@ describe('Library', () => { formats: [{ package_version: "1", mime_type: 'application/ts+book', + imported: false, modified_at: 20151222120130, url: 'https://api.unfoldingword.org/ts/txt/2/gen/en/ulb/source.json' }], project_id: project.id }; resourceAlt = alter(resource, ['name']); + resourceAlt.formats[0].imported = true; }); it('should add a resource to the database', () => { testInsert('addResource', 'getResource', resource, [project.id], [source_language.slug, project.slug], - ['id', 'project_id']); + ['id', 'project_id', 'imported']); }); it('should update a resource in the database', () => { testUpdate('addResource', 'getResource', resource, resourceAlt, [source_language.id, project.id], [source_language.slug, project.slug], - ['id', 'project_id']); + ['id', 'project_id', 'imported']); }); it('should not add incomplete resource to the database', () => { diff --git a/__tests__/main-tests.js b/__tests__/main-tests.js index 5b24fb8..67482e7 100644 --- a/__tests__/main-tests.js +++ b/__tests__/main-tests.js @@ -265,7 +265,6 @@ describe('Client', () => { }); }); - it('should not download a missing resource container', () => { library.__queueResponse = null; @@ -467,6 +466,68 @@ describe('Client', () => { }); }); +describe('Import', () => { + var client, library, fs, rc; + + beforeEach(() => { + jest.mock('fs'); + jest.mock('rimraf'); + jest.mock('ncp'); + + fs = require('fs'); + rc = require('resource-container'); + var Client = require('../'); + + fs.writeFileSync(config.schemaPath, ''); + fs.writeFileSync(config.dbPath, ''); + + client = new Client(config.dbPath, config.resDir); + var Library = require('../lib/library'); + library = new Library(null); + }); + + it('should import a resource container', () => { + let fs = require('fs'); + let archiveDir = path.join(config.resDir, 'en_obs_obs'); + let archiveFile = archiveDir + '.tsrc'; + fs.writeFileSync(archiveFile, 'some file'); + mkdirp(archiveDir); + + rc.__queueResponse = { + slug: 'en-obs-obs', + language: { slug: 'en'}, + project: { slug: 'gen'}, + resource: { slug: 'ulb', status: { checking_level: '2'}}, + get info() { return {}; } + }; + + library.__queueResponse = { + id: 1, + slug: 'ulb', + formats: [ + { + syntax_version: '0.1', + mime_type: 'application/tsrc+book', + modified_at: 0, + url: 'some/url', + } + ] + }; + + fs.writeFileSync('/container_to_import/package.json', ''); + fs.writeFileSync('container_to_import/LICENSE.md', 'some license'); + fs.writeFileSync('container_to_import/content/config.yml', ''); + + return client.importResourceContainer('/container_to_import') + .then((container) => { + // just making sure no errors are throw + }) + .catch(function(err) { + throw err; + }); + }); +}); + describe('Update check', () => { var client, request, library, fs, rc; diff --git a/lib/library.js b/lib/library.js index 82401df..7506c9d 100644 --- a/lib/library.js +++ b/lib/library.js @@ -309,6 +309,7 @@ function Library(sqliteHelper, opts) { mime_type: format.mime_type, modified_at: format.modified_at || 0, url: format.url, + imported: format.imported ? 1 : 0, resource_id: resourceId }, ['mime_type', 'resource_id']); } @@ -661,6 +662,7 @@ function Library(sqliteHelper, opts) { for(var format of formatResults) { delete format.id; delete format.resource_id; + format.imported = format.imported ? true : false; res.formats.push(format); // TRICKY: this is not technically correct, but for convenience we attach the import status to the resource directly if(format.imported) res.imported = true; @@ -736,6 +738,7 @@ function Library(sqliteHelper, opts) { for(var format of formatResults) { delete format.id; delete format.resource_id; + format.imported = format.imported ? true : false; res.formats.push(format); // TRICKY: this is not technically correct, but for convenience we attach the import status to the resource directly if(format.imported) res.imported = true; diff --git a/lib/main.js b/lib/main.js index 10d896d..12899e9 100644 --- a/lib/main.js +++ b/lib/main.js @@ -11,8 +11,8 @@ const request = require('./request'), rimraf = require('rimraf'), Library = require('./library'), mv = require('mv'), - rc = require('resource-container'); - // ncp = require('ncp').ncp; + rc = require('resource-container'), + ncp = require('ncp').ncp; /** * Initializes a new api client. @@ -964,7 +964,7 @@ function Client(dbPath, resourceDir, opts) { let destination = path.join(resourceDir, container.slug); // delete the old container - rimraf.sync(path); + rimraf.sync(destination); return new Promise(function(resolve, reject) { ncp(containerPath, destination, function(err) { if(err) { @@ -978,7 +978,7 @@ function Client(dbPath, resourceDir, opts) { // NOTE: we should technically remove the old formats, but we only use the rc format so we can ignore the rest. let resource = container.resource; resource.formats = [{ - package_version: container.info().package_version, + package_version: container.info.package_version, mime_type: container.resource.type, modified_at: container.resource.date_modified, imported: 1,