const minisignExpress = require('../lib/express-minisign');

const chai = require('chai');
chai.should();
const assert = require('assert');
const request = require('supertest');
const fs = require('fs');
const Minisign = require('minisign');
const sodium = require('sodium-native');

describe('express-minisign', function () {
	let password;
	let publicKey;
	before(function () {
		this.timeout(100000);
		password = 'express-minisign-tests';
		const passwordBuffer = Buffer.from(password);
		let securePassword = sodium.sodium_malloc(passwordBuffer.byteLength);
		securePassword.fill(passwordBuffer);
		const keyPair = Minisign.keypairGen(securePassword);
		const {PK, SK} = Minisign.formatKeys(keyPair);
		fs.writeFileSync(__dirname + '/helpers/minisign/.minisign.key', SK);
		fs.writeFileSync(__dirname + '/helpers/minisign/minisign.pub', PK);
		publicKey = fs.readFileSync(__dirname + '/helpers/minisign/minisign.pub');
	});
  it('exports a function', function () {
    minisignExpress.should.be.a('function');
  });
	it('should throw when a minisignPassword has not been included in the options', function() {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
			serveFile: getFile,
		};

		app.use([
			express.json()
		]);
		assert.throws(() => {app.use('/download', minisignExpress(options))}, Error);
	});
	it('should throw when a minisignSecretKeyFilePath has not been included in the options', function() {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			serveFile: getFile,
		};

		app.use([
			express.json()
		]);
		assert.throws(() => {app.use('/download', minisignExpress(options))}, Error);
	});
	it('should throw when a serveFile function has not been included in the options', function() {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
		};

		app.use([
			express.json()
		]);
		assert.throws(() => {app.use('/download', minisignExpress(options))}, Error);
	});
	it('should throw when a filePath string has not been included in the options', function() {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
		};

		app.use([
			express.json()
		]);
		assert.throws(() => {app.use('/download', minisignExpress(options))}, Error);
	});
	it('should throw when both a filePath string and a serveFile have been included in the options', function() {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
			serveFile: getFile,
			filePath: __dirname+'/helpers/smallFile.txt'
		};

		app.use([
			express.json()
		]);
		assert.throws(() => {app.use('/download', minisignExpress(options))}, Error);
	});

	it('integrates with an express server', function (done) {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
			serveFile: getFile,
		};

		app.use([
			express.json()
		]);
		app.use('/download', minisignExpress(options));
		request(app)
			.post('/download')
			.send({filename: 'smallFile.txt'})
			.set('Accept', 'application/json')
			.expect(200)
			.expect(function (res) {
				res.header['x-file-minisign-signature'].should.be.a('string');
			})
			.end(done);
	});
	it('works with small files', function (done) {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
			serveFile: getFile,
		};

		app.use([
			express.json()
    ]);

		app.use('/download', minisignExpress(options));
		request(app)
			.post('/download')
			.send({filename: 'smallFile.txt'})
			.set('Accept', 'application/json')
			.expect(200)
			.expect(canVerifyKey)
			.end(done);
	});
	it('works with large files', function (done) {
		this.timeout(100000);
		const express = require('express');
		const app = require('express')();
		const getFile = (fileName) => {
			return {
				filePath: __dirname+'/helpers/'+fileName
			};
		}
		const options = {
			minisignPassword: password,
			minisignSecretKeyFilePath: __dirname+'/helpers/minisign/.minisign.key',
			serveFile: getFile,
		};

		app.use([
			express.json()
		]);

		app.use('/download', minisignExpress(options));
		request(app)
			.post('/download')
			.send({filename: 'largeFile.txt'})
			.set('Accept', 'application/json')
			.expect(200)
			.expect(canVerifyKey)
			.end(done);
	});

	function canVerifyKey(res) {
		const rand = Math.round(Math.random() * 1000);
		fs.writeFileSync(__dirname+'/helpers/downloadFile'+rand+'.txt', res.text);
		const downloadedFile = fs.readFileSync(__dirname+'/helpers/downloadFile' + rand + '.txt');
		const publicKeyInfo = Minisign.parsePubKey(publicKey);
		const signature = Minisign.parseSignature(Buffer.from(res.header['x-file-minisign-signature'], 'base64'));
		const verified = Minisign.verifySignature(signature, downloadedFile, publicKeyInfo);
		if (!(verified.should.be.true)) throw new Error('Signature does not match!');
	}
});
