Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 45 additions & 34 deletions api/main_endpoints/routes/Printer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ const {
const {
PRINTING = {}
} = require('../../config/config.json');
const AuditLogActions = require('../util/auditLogActions.js');
const AuditLog = require('../models/AuditLog.js');

// see https://github.com/SCE-Development/Quasar/tree/dev/docker-compose.dev.yml#L11
let PRINTER_URL = process.env.PRINTER_URL
|| 'http://localhost:14000';
let PRINTER_URL = process.env.PRINTER_URL || 'http://localhost:14000';

const router = express.Router();

Expand All @@ -36,7 +37,7 @@ const storage = multer.diskStorage({
filename: function(req, file, cb) {
const uniqueSuffix = Date.now();
cb(null, uniqueSuffix + '_' + file.originalname);
}
},
});

const upload = multer({ storage });
Expand All @@ -58,7 +59,9 @@ router.get('/healthCheck', async (req, res) => {
* https://github.com/SCE-Development/Quasar/wiki/How-do-Health-Checks-Work%3F
*/
if (!PRINTING.ENABLED) {
logger.warn('Printing is disabled, returning 200 to mock the printing server');
logger.warn(
'Printing is disabled, returning 200 to mock the printing server'
);
return res.sendStatus(OK);
}
await axios
Expand All @@ -74,24 +77,19 @@ router.get('/healthCheck', async (req, res) => {
});

router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => {
let totalFileSize = 0;

if (!checkIfTokenSent(req)) {
logger.warn('/sendPrintRequest was requested without a token');
return res.sendStatus(UNAUTHORIZED);
}
if (!await decodeToken(req)) {
const user = decodeToken(req);
if (!user) {
logger.warn('/sendPrintRequest was requested with an invalid token');
return res.sendStatus(UNAUTHORIZED);
}
if (!PRINTING.ENABLED) {
logger.warn('Printing is disabled, returning 200 and dummy print id to mock the printing server');
return res.status(OK).send({ printId: null });
}

const dir = path.join(__dirname, 'printing');
const { totalChunks, chunkIdx } = req.body;

// reassemble pdf on last chunk received
if (Number(chunkIdx) < totalChunks - 1) {
logger.warn('Printing is disabled, returning 200 to mock the printing server');
return res.sendStatus(OK);
}

Expand All @@ -106,6 +104,7 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => {

try {
const chunkData = await fs.promises.readFile(path.join(dir, chunk));
totalFileSize += chunkData.length;
fs.appendFileSync(assembledPdfFromChunks, chunkData);
} catch (err) {
logger.error('/sendPrintRequest encountered an error while assembling pdf: ' + err);
Expand All @@ -116,31 +115,43 @@ router.post('/sendPrintRequest', upload.single('chunk'), async (req, res) => {

const stream = await fs.createReadStream(assembledPdfFromChunks);
const data = new FormData();
data.append('file', stream, {filename: id, type: 'application/pdf'});
data.append('file', fs.createReadStream(file.path), { filename: file.originalname });
data.append('copies', copies);
data.append('sides', sides);

try {
// full pdf can be sent to quasar no problem
const printRes = await axios.post(PRINTER_URL + '/print', data, {
axios.post(PRINTER_URL + '/print',
data,
{
headers: {
...data.getHeaders(),
},
maxContentLength: 1024 * 1024 * 150, // 150 mb
maxBodyLength: Infinity
}
})
.then( async () => {

// create audit log on print
await AuditLog.create({
userId: user._id,
action: AuditLogActions.PRINT_PAGE,
details: {
copies: parseInt(copies),
sides,
fileSize: totalFileSize,
userEmail: user.email,
printedAt: new Date(),
printJobId: id
}
}).catch(logger.error);

// delete file from temp folder after printing
fs.unlink(file.path, (err) => {
if (err) {
logger.error(`Unable to delete file at path ${file.path}:`, err);
}
});
res.sendStatus(OK);
}).catch((err) => {
logger.error('/sendPrintRequest had an error: ', err);
res.sendStatus(SERVER_ERROR);
});

// { print_id: null | string }
const printId = printRes.data;

await cleanUpChunks(dir, id);
res.status(OK).send(printId);
} catch (err) {
logger.error('/sendPrintRequest had an error: ', err);

await cleanUpChunks(dir, id);
res.sendStatus(SERVER_ERROR);
}
});

module.exports = router;
53 changes: 50 additions & 3 deletions test/api/Printer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
process.env.NODE_ENV = 'test';

const { PRINTING = {} } = require('../../api/config/config.json');
const mongoose = require('mongoose');
const chai = require('chai');
const chaiHttp = require('chai-http');
const fs = require('fs');
const User = require('../../api/main_endpoints/models/User.js');

const {
OK,
Expand All @@ -25,6 +27,10 @@ const tools = require('../util/tools/tools.js');
const crypto = require('crypto');
const token = '';
const printerUtil = require('../../api/main_endpoints/util/Printer.js');
const { MEMBERSHIP_STATE } = require('../../api/util/constants');

const AuditLogActions = require('../../api/main_endpoints/util/auditLogActions.js');
const AuditLog = require('../../api/main_endpoints/models/AuditLog.js');

let app = null;
let test = null;
Expand All @@ -41,6 +47,7 @@ describe('Printer', () => {
__dirname + '/../../api/main_endpoints/routes/Printer.js',
]);
test = new SceApiTester(app);

done();
});

Expand All @@ -50,14 +57,19 @@ describe('Printer', () => {
tools.terminateServer(done);
});

beforeEach(() => {
beforeEach(async () => {
await User.deleteMany({});
await AuditLog.deleteMany({});
setTokenStatus(false);
});

afterEach(() => {
afterEach(async () => {
await User.deleteMany({});
await AuditLog.deleteMany({});
resetTokenMock();
});

const url = '/api/Printer/sendPrintRequest';
describe('cleanUpExpiredChunks', () => {
const CHUNK_DIRECTORY = __dirname + '/../../api/main_endpoints/routes/printing';
const MY_BIRTH_DATE = new Date('December 4, 2005 07:53:00');
Expand Down Expand Up @@ -126,6 +138,7 @@ describe('Printer', () => {
expect(result).to.have.status(UNAUTHORIZED);
});


it(`Should successfully process all ${TOTAL_CHUNKS} chunks sent (with valid token)`, async () => {
let chunksProcessed = 0;
setTokenStatus(true);
Expand Down Expand Up @@ -154,5 +167,39 @@ describe('Printer', () => {

expect(chunksProcessed).to.equal(TOTAL_CHUNKS);
});

it('Should create only one audit log in the database when the response status is 200', async () => {
// Skip tests if printing is disabled
if (!PRINTING.ENABLED) {
return;
}

const userId = new mongoose.Types.ObjectId();
const user = new User({
_id: userId,
firstName: 'first_name',
lastName: 'last_name',
email: '[email protected]',
password: 'Passw0rd123',
emailVerified: true,
accessLevel: MEMBERSHIP_STATE.MEMBER,
});
await user.save();

setTokenStatus(true, {
_id: user._id,
email: user.email,
accessLevel: user.accessLevel,
});

const result = await test.sendPostRequestWithToken(token, url, { DUMMY_CHUNK });
const auditEntry = await AuditLog.findOne({
action: AuditLogActions.PRINT_PAGE,
}).lean();

expect(result).to.have.status(OK);
expect(auditEntry).to.exist;
expect(await AuditLog.countDocuments()).to.equal(1);
});
});
});
4 changes: 2 additions & 2 deletions test/api/ShortcutSearch.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,8 +263,8 @@ describe('ShortcutSearch', () => {
});
});

after(() => {
User.deleteMany({});
after(async () => {
await User.deleteMany({});
});
});
});
5 changes: 3 additions & 2 deletions test/api/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -835,9 +835,10 @@ describe('User', () => {
expect(result.body.newAnnualMembers).to.equal(2);
});

after(() => {
after(async () => {
revertClock();
User.deleteMany({});
await User.deleteMany({});
await AuditLog.deleteMany({});
});
});
});
Expand Down