'use strict'; /* * Import and Export tests for the /p/whateverPadId/import and /p/whateverPadId/export endpoints. */ import {MapArrayType} from "../../../../node/types/MapType"; import {SuperTestStatic} from "supertest"; import TestAgent from "supertest/lib/agent"; const assert = require('assert').strict; const common = require('../../common'); const fs = require('fs'); const settings = require('../../../../node/utils/Settings'); const superagent = require('superagent'); const padManager = require('../../../../node/db/PadManager'); const plugins = require('../../../../static/js/pluginfw/plugin_defs'); const padText = fs.readFileSync(`${__dirname}/test.txt`); const etherpadDoc = fs.readFileSync(`${__dirname}/test.etherpad`); const wordDoc = fs.readFileSync(`${__dirname}/test.doc`); const wordXDoc = fs.readFileSync(`${__dirname}/test.docx`); const odtDoc = fs.readFileSync(`${__dirname}/test.odt`); const pdfDoc = fs.readFileSync(`${__dirname}/test.pdf`); let agent: TestAgent; const apiVersion = 1; const testPadId = makeid(); const testPadIdEnc = encodeURIComponent(testPadId); const deleteTestPad = async () => { if (await padManager.doesPadExist(testPadId)) { const pad = await padManager.getPad(testPadId); await pad.remove(); } }; describe(__filename, function () { this.timeout(45000); before(async function () { agent = await common.init(); }); describe('Connectivity', function () { it('can connect', async function () { await agent.get('/api/') .set("authorization", await common.generateJWTToken()) .expect(200) .expect('Content-Type', /json/); }); }); describe('API Versioning', function () { it('finds the version tag', async function () { await agent.get('/api/') .set("authorization", await common.generateJWTToken()) .expect(200) .expect((res:any) => assert(res.body.currentVersion)); }); }); /* Tests ----- Test. / Create a pad / Set pad contents / Try export pad in various formats / Get pad contents and ensure it matches imported contents Test. / Try to export a pad that doesn't exist // Expect failure Test. / Try to import an unsupported file to a pad that exists -- TODO: Test. Try to import to a file and abort it half way through Test. Try to import to files of varying size. Example Curl command for testing import URI: curl -s -v --form file=@/home/jose/test.txt http://127.0.0.1:9001/p/foo/import */ describe('Imports and Exports', function () { const backups:MapArrayType = {}; beforeEach(async function () { backups.hooks = {}; for (const hookName of ['preAuthorize', 'authenticate', 'authorize']) { backups.hooks[hookName] = plugins.hooks[hookName]; plugins.hooks[hookName] = []; } // Note: This is a shallow copy. backups.settings = Object.assign({}, settings); settings.requireAuthentication = false; settings.requireAuthorization = false; settings.users = {user: {password: 'user-password'}}; }); afterEach(async function () { Object.assign(plugins.hooks, backups.hooks); // Note: This does not unset settings that were added. Object.assign(settings, backups.settings); }); it('creates a new Pad, imports content to it, checks that content', async function () { await agent.get(`${endPoint('createPad')}?padID=${testPadId}`) .set("authorization", await common.generateJWTToken()) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.equal(res.body.code, 0)); await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'}) .expect(200); await agent.get(`${endPoint('getText')}?padID=${testPadId}`) .set("authorization", await common.generateJWTToken()) .expect(200) .expect((res:any) => assert.equal(res.body.data.text, padText.toString())); }); describe('export from read-only pad ID', function () { let readOnlyId:string; // This ought to be before(), but it must run after the top-level beforeEach() above. beforeEach(async function () { if (readOnlyId != null) return; await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', padText, {filename: '/test.txt', contentType: 'text/plain'}) .expect(200); const res = await agent.get(`${endPoint('getReadOnlyID')}?padID=${testPadId}`) .set("authorization", await common.generateJWTToken()) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.equal(res.body.code, 0)); readOnlyId = res.body.data.readOnlyID; }); for (const authn of [false, true]) { describe(`requireAuthentication = ${authn}`, function () { // This ought to be before(), but it must run after the top-level beforeEach() above. beforeEach(async function () { settings.requireAuthentication = authn; }); for (const exportType of ['html', 'txt', 'etherpad']) { describe(`export to ${exportType}`, function () { let text:string; // This ought to be before(), but it must run after the top-level beforeEach() above. beforeEach(async function () { if (text != null) return; let req = agent.get(`/p/${readOnlyId}/export/${exportType}`) .set("authorization", await common.generateJWTToken()); if (authn) req = req.auth('user', 'user-password'); const res = await req .expect(200) .buffer(true).parse(superagent.parse.text); text = res.text; }); it('export OK', async function () { assert.match(text, /This is the/); }); it('writable pad ID is not leaked', async function () { assert(!text.includes(testPadId)); }); it('re-import to read-only pad ID gives 403 forbidden', async function () { let req = agent.post(`/p/${readOnlyId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', Buffer.from(text), { filename: `/test.${exportType}`, contentType: 'text/plain', }); if (authn) req = req.auth('user', 'user-password'); await req.expect(403); }); it('re-import to read-write pad ID gives 200 OK', async function () { // The new pad ID must differ from testPadId because Etherpad refuses to import // .etherpad files on top of a pad that already has edits. let req = agent.post(`/p/${testPadId}_import/import`) .set("authorization", await common.generateJWTToken()) .attach('file', Buffer.from(text), { filename: `/test.${exportType}`, contentType: 'text/plain', }); if (authn) req = req.auth('user', 'user-password'); await req.expect(200); }); }); } }); } }); describe('Import/Export tests requiring AbiWord/LibreOffice', function () { before(async function () { if ((!settings.abiword || settings.abiword.indexOf('/') === -1) && (!settings.soffice || settings.soffice.indexOf('/') === -1)) { this.skip(); } }); // For some reason word import does not work in testing.. // TODO: fix support for .doc files.. it('Tries to import .doc that uses soffice or abiword', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', wordDoc, {filename: '/test.doc', contentType: 'application/msword'}) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.deepEqual(res.body, { code: 0, message: 'ok', data: {directDatabaseAccess: false}, })); }); it('exports DOC', async function () { await agent.get(`/p/${testPadId}/export/doc`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) .expect((res:any) => assert(res.body.length >= 9000)); }); it('Tries to import .docx that uses soffice or abiword', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', wordXDoc, { filename: '/test.docx', contentType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', }) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.deepEqual(res.body, { code: 0, message: 'ok', data: {directDatabaseAccess: false}, })); }); it('exports DOC from imported DOCX', async function () { await agent.get(`/p/${testPadId}/export/doc`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) .expect((res:any) => assert(res.body.length >= 9100)); }); it('Tries to import .pdf that uses soffice or abiword', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', pdfDoc, {filename: '/test.pdf', contentType: 'application/pdf'}) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.deepEqual(res.body, { code: 0, message: 'ok', data: {directDatabaseAccess: false}, })); }); it('exports PDF', async function () { await agent.get(`/p/${testPadId}/export/pdf`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) .expect((res:any) => assert(res.body.length >= 1000)); }); it('Tries to import .odt that uses soffice or abiword', async function () { await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', odtDoc, {filename: '/test.odt', contentType: 'application/odt'}) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.deepEqual(res.body, { code: 0, message: 'ok', data: {directDatabaseAccess: false}, })); }); it('exports ODT', async function () { await agent.get(`/p/${testPadId}/export/odt`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse['application/octet-stream']) .expect(200) .expect((res:any) => assert(res.body.length >= 7000)); }); }); // End of AbiWord/LibreOffice tests. it('Tries to import .etherpad', async function () { this.timeout(3000); await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', etherpadDoc, { filename: '/test.etherpad', contentType: 'application/etherpad', }) .expect(200) .expect('Content-Type', /json/) .expect((res:any) => assert.deepEqual(res.body, { code: 0, message: 'ok', data: {directDatabaseAccess: true}, })); }); it('exports Etherpad', async function () { this.timeout(3000); await agent.get(`/p/${testPadId}/export/etherpad`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse.text) .expect(200) .expect(/hello/); }); it('exports HTML for this Etherpad file', async function () { this.timeout(3000); await agent.get(`/p/${testPadId}/export/html`) .set("authorization", await common.generateJWTToken()) .expect(200) .expect('content-type', 'text/html; charset=utf-8') .expect(/