diff --git a/.eslintrc b/.eslintrc index 67f0307..e5b460e 100644 --- a/.eslintrc +++ b/.eslintrc @@ -21,7 +21,7 @@ "strict": 1, "new-cap": 0, "camelcase": 0, - "comma-dangle": 1, + "comma-dangle": 0, "no-unused-vars": 1, "comma-spacing": 1, "space-infix-ops": 1, diff --git a/src/index.ts b/src/index.ts index 21e80b4..2bd02fb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -515,16 +515,27 @@ class DicoogleAccess { * @param callback the function to call on completion */ unindex(uri: string | string[], provider: string | string[], callback?: (error: any) => void): Promise; - unindex(uri: string | string[], provider: string | string[] | ((error: any) => void), callback?: (error: any) => void) { + unindex(uri: string | string[], provider?: string | string[] | ((error: any) => void), callback?: (error: any) => void) { if (typeof provider === 'function' && !callback) { callback = provider; provider = undefined; } - return andCallVoid(this.request('POST', Endpoints.UNINDEX) - .query({ - uri, - provider - }), callback); + + if (Array.isArray(uri) && uri.length > 1) { + // send URIs as form data to prevent URI from being too long + let body = uri.map(uri => 'uri=' + encodeURIComponent(uri)).join('&'); + + return andCallVoid(this.request('POST', Endpoints.UNINDEX) + .type('form') + .query({provider}) + .send(body), callback); + } else { + return andCallVoid(this.request('POST', Endpoints.UNINDEX) + .query({ + uri, + provider + }), callback); + } }; /** Request that the file at the given URI is permanently removed. The operation, unlike index(), is not recursive. @@ -533,10 +544,17 @@ class DicoogleAccess { * @param callback the function to call on completion */ remove(uri: string | string[], callback?: (error: any) => void): Promise { - return andCallVoid(this.request('POST', Endpoints.REMOVE) - .query({ - uri - }), callback); + if (Array.isArray(uri) && uri.length > 1) { + // send URIs as form data to prevent URI from being too long + return andCallVoid(this.request('POST', Endpoints.REMOVE) + .type('form') + .send(uri.map(uri => 'uri=' + encodeURIComponent(uri)).join('&')), callback); + } else { + return andCallVoid(this.request('POST', Endpoints.REMOVE) + .query({ + uri + }), callback); + } }; /** Retrieve the running Dicoogle version. diff --git a/src/util.ts b/src/util.ts index add30e8..f9b7766 100644 --- a/src/util.ts +++ b/src/util.ts @@ -25,7 +25,7 @@ export function isDicomUUID(uid: string): boolean { return uid.length <= 64 && uid.match(/^\d+(\.\d+)*$/) !== null; } -export function andCall(promise: Promise, callback?: (error: any, outcome?: T) => void): Promise { +export function andCall(promise: Promise, callback: (error: any, outcome?: T) => void | undefined): Promise { if (callback) { promise.then( (value) => callback(null, value), @@ -34,7 +34,7 @@ export function andCall(promise: Promise, callback?: (error: any, outcome? return promise; } -export function andCallVoid(promise: Promise, callback?: (error: any) => void): Promise { +export function andCallVoid(promise: Promise, callback: (error: any) => void | undefined): Promise { if (callback) { promise.then( () => callback(null), diff --git a/test/.eslintrc b/test/.eslintrc index fd94e60..ead9d97 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -21,7 +21,7 @@ "global-strict": 0, "new-cap": 0, "camelcase": 0, - "comma-dangle": 1, + "comma-dangle": 0, "no-unused-vars": 1, "comma-spacing": 1, "space-infix-ops": 1, diff --git a/test/mock/service-mock.js b/test/mock/service-mock.js index dfdf790..4e9e51c 100644 --- a/test/mock/service-mock.js +++ b/test/mock/service-mock.js @@ -22,6 +22,13 @@ const nock = require('nock'); const URL = require('url'); const qs = require('querystring'); +function validateURI(uri) { + if (typeof uri !== 'string') { + return false; + } + return /^(file:)?[\.-\w\/\%\?\&\=]+$/.test(uri); +} + /** Use nock to intercept Dicoogle client requests. * @param {number} [port] the TCP port to listen to * @returns {object} a Dicoogle access object Dicoogle access object connected to a mock Dicoogle server. @@ -34,7 +41,7 @@ module.exports = function createDicoogleMock(port = 8080) { const SEARCH_RESULTS = [ { - uri: '/opt/dataset/file1', + uri: 'file:/opt/dataset/file1', fields: { "Modality": "MR", "StudyInstanceUID": "1.2.3.4.5.6.7777777", @@ -48,7 +55,7 @@ module.exports = function createDicoogleMock(port = 8080) { } }, { - uri: '/opt/dataset/file2', + uri: 'file:/opt/dataset/file2', fields: { "Modality": "MR", "StudyInstanceUID": "1.2.3.4.5.6.7777777", @@ -79,10 +86,10 @@ module.exports = function createDicoogleMock(port = 8080) { serieDescription: "", serieModality: "CR", images: [{ - uri: '/opt/dataset/file1', + uri: 'file:/opt/dataset/file1', "sopInstanceUID": "1.2.3.4.5.6.7777777.4444.1" }, { - uri: '/opt/dataset/file2', + uri: 'file:/opt/dataset/file2', "sopInstanceUID": "1.2.3.4.5.6.7777777.4444.2" }] }] @@ -299,27 +306,50 @@ module.exports = function createDicoogleMock(port = 8080) { // mock index on specific provider .post('/management/tasks/index') - .query({ uri: /[a-z0-9\-/]+/, plugin: /.*/ }) + .query(({ uri, plugin }) => { + return validateURI(uri) && /\w+/.test(plugin); + }) .reply(200) // mock index on all providers .post('/management/tasks/index') - .query({ uri: /[a-z0-9\-/]+/ }) + .query(({ uri }) => validateURI(uri)) .reply(200) - // mock unindex on all providers + // mock unindex on all providers (via query string) .post('/management/tasks/unindex') - .query({ uri: /[a-z0-9\-/]+/ }) + .query(({ uri }) => validateURI(uri)) + .reply(200) + + // mock unindex on all providers (via form data) + .post('/management/tasks/unindex', ({ uri: uris }) => { + return uris.length > 0 && uris.every(validateURI); + }) .reply(200) - // mock unindex on specific provider + // mock unindex on specific provider (via query string) .post('/management/tasks/unindex') - .query({ uri: /[a-z0-9\-/]+/, provider: /.*/ }) + .query(({ uri, plugin }) => { + return validateURI(uri) && /\w+/.test(plugin); + }) .reply(200) - // mock remove + // mock unindex on specific provider (via form data) + .post('/management/tasks/unindex', ({ uri: uris }) => { + return uris.length > 0 && uris.every(validateURI); + }) + .query({ provider: /\w+/ }) + .reply(200) + + // mock remove (via query string) .post('/management/tasks/remove') - .query({ uri: /[a-z0-9\-/]+/ }) + .query(({ uri }) => validateURI(uri)) + .reply(200) + + // mock remove (via form data) + .post('/management/tasks/remove', ({ uri: uris }) => { + return uris.length > 0 && uris.every(validateURI); + }) .reply(200) // mock dump diff --git a/test/test-base-promise.js b/test/test-base-promise.js index 28ea632..c57653c 100644 --- a/test/test-base-promise.js +++ b/test/test-base-promise.js @@ -204,7 +204,7 @@ describe('Dicoogle Client, Promise API (under Node.js)', function() { describe('#index() on all providers', function() { it("should say ok with no error", async function() { - await dicoogle.index('/opt/another-dataset'); + await dicoogle.index('file:/opt/another-dataset'); }); }); }); @@ -212,20 +212,40 @@ describe('Dicoogle Client, Promise API (under Node.js)', function() { describe('Unindex', function() { describe('#unindex() on one provider', function() { it("should say ok with no error", async function() { - await dicoogle.unindex('/opt/another-dataset/1_1.dcm', 'lucene'); + await dicoogle.unindex('file:/opt/another-dataset/1_1.dcm', 'lucene'); }); }); describe('#unindex() on all providers', function() { it("should say ok with no error", async function() { - await dicoogle.unindex('/opt/another-dataset/1_1.dcm'); + await dicoogle.unindex('file:/opt/another-dataset/1_1.dcm'); + }); + }); + + describe('#unindex() multiple URIs', function() { + it("should say ok with no error", async function() { + await dicoogle.unindex([ + 'file:/opt/another-dataset/1_1.dcm', + 'file:/opt/another-dataset/1_2.dcm', + ]); }); }); }); - describe('#remove() a file', function() { - it("should say ok with no error", async function() { - await dicoogle.remove('/opt/another-dataset/1_1.dcm'); + describe("Remove", function() { + describe('#remove() a file', function() { + it("should say ok with no error", async function() { + await dicoogle.remove('file:/opt/another-dataset/1_1.dcm'); + }); + }); + describe('#remove() multiple files', function() { + it("should say ok with no error", async function() { + await dicoogle.remove([ + 'file:/opt/another-dataset/1_1.dcm', + 'file:/opt/another-dataset/1_2.dcm', + 'file:/opt/another-dataset/1_3.dcm', + ]); + }); }); }); diff --git a/test/test-base.js b/test/test-base.js index cc2e3af..f593ef3 100644 --- a/test/test-base.js +++ b/test/test-base.js @@ -282,7 +282,7 @@ describe('Dicoogle Client, callback API (under Node.js)', function() { describe('Index', function() { describe('#index() on one provider', function() { it("should say ok with no error", function (done) { - Dicoogle.index('/opt/another-dataset', 'lucene', function(error) { + Dicoogle.index('file:/opt/another-dataset', 'lucene', function(error) { assert.equal(error, null); done(); }); @@ -302,7 +302,7 @@ describe('Dicoogle Client, callback API (under Node.js)', function() { describe('Unindex', function() { describe('#unindex() on one provider', function() { it("should say ok with no error", function (done) { - Dicoogle.unindex('/opt/another-dataset/1_1.dcm', 'lucene', function(error) { + Dicoogle.unindex('file:/opt/another-dataset/1_1.dcm', 'lucene', function(error) { assert.equal(error, null); done(); }); @@ -311,20 +311,46 @@ describe('Dicoogle Client, callback API (under Node.js)', function() { describe('#unindex() on all providers', function() { it("should say ok with no error", function (done) { - Dicoogle.unindex('/opt/another-dataset/1_1.dcm', function(error) { + Dicoogle.unindex('file:/opt/another-dataset/1_1.dcm', function(error) { assert.equal(error, null); done(); }); }); }); + + describe('#unindex() multiple URIs', function() { + it("should say ok with no error", function (done) { + Dicoogle.unindex([ + 'file:/opt/another-dataset/1_1.dcm', + 'file:/opt/another-dataset/1_2.dcm', + ], function(error) { + assert.equal(error, null); + done(); + }); + }); + }); }); - describe('#remove() a file', function() { - it("should say ok with no error", function (done) { - Dicoogle.remove('/opt/another-dataset/1_1.dcm', function(error) { - assert.equal(error, null); - done(); - }); + describe("Remove", function() { + describe('#remove() a file', function() { + it("should say ok with no error", function (done) { + Dicoogle.remove('file:/opt/another-dataset/1_1.dcm', function(error) { + assert.equal(error, null); + done(); + }); + }); + }); + describe('#remove() multiple files', function() { + it("should say ok with no error", function (done) { + Dicoogle.remove([ + 'file:/opt/another-dataset/1_1.dcm', + 'file:/opt/another-dataset/1_2.dcm', + 'file:/opt/another-dataset/1_3.dcm', + ], function(error) { + assert.equal(error, null); + done(); + }); + }); }); });