import sharp from 'sharp' import { Eltro as t, assert} from 'eltro' import fs from 'fs/promises' import { fileURLToPath } from 'url' import path from 'path' import { createClient, startServer, log, resetLog } from '../helper.server.mjs' import encode from '../../api/jwt/encode.mjs' let __dirname = path.dirname(fileURLToPath(import.meta.url)) function resolve(file) { return path.resolve(path.join(__dirname, file)) } const currYear = new Date().getFullYear().toString() t.describe('Media (API)', () => { let client let secret = 'asdf1234' let testFiles = [] t.before(function() { client = createClient() return startServer() }) t.after(function() { return Promise.all(testFiles.map(function(file) { return fs.unlink(resolve(`../../public/${file}`)).catch(function() {}) })) .then(function() { // return fs.unlink(resolve('../../public/existing/test.png')).catch(function() {}) }) }) t.timeout(10000).describe('POST /media', function temp() { t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected( client.upload('/media', resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected( client.upload('/media?token=' + assertToken, resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should upload file and create file', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media?token=${token}`, resolve('test.png') ) ) testFiles.push(data.path) assert.ok(data) assert.ok(data.filename) assert.ok(data.filename.startsWith(currYear)) assert.ok(data.path) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve(`../../public/${data.path}`)), ]) assert.strictEqual(stats[0].size, stats[1].size) let img = await sharp(resolve(`../../public/${data.path}`)).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') }) }) t.timeout(10000).describe('POST /media/noprefix', function temp() { t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected( client.upload('/media/noprefix', resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected( client.upload('/media/noprefix?token=' + assertToken, resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should upload and create file with no prefix', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media/noprefix?token=${token}`, resolve('test.png') ) ) testFiles.push(data.path) assert.ok(data) assert.strictEqual(data.filename, 'test.png') assert.ok(data.path) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve('../../public/development/test.png')), ]) assert.strictEqual(stats[0].size, stats[1].size) let img = await sharp(resolve('../../public/development/test.png')).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') }) t.test('should upload and create file with prefix if exists', async () => { let token = encode(null, { iss: 'development' }, secret) await fs.unlink(resolve(`../../public/development/test.png`)).catch(function() {}) let datas = [] datas.push(await client.upload( `/media/noprefix?token=${token}`, resolve('test.png') )) datas.push(await client.upload( `/media/noprefix?token=${token}`, resolve('test.png') )) testFiles.push(datas[0].path) testFiles.push(datas[1].path) let prefix = new Date().toISOString().replace(/-/g,'').split('T')[0] let first = datas[0] let second = datas[1] assert.notStrictEqual(first.filename, second.filename) assert.notOk(first.filename.startsWith(currYear)) assert.notOk(first.filename.startsWith(prefix)) assert.ok(second.filename.startsWith(currYear)) assert.ok(second.filename.startsWith(prefix)) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve('../../public/development/test.png')), fs.stat(resolve('../../public/development/' + second.filename)), ]) assert.strictEqual(stats[0].size, stats[1].size) assert.strictEqual(stats[0].size, stats[2].size) }) }) t.timeout(10000).describe('POST /media/resize', function temp() { t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected( client.upload('/media/resize', resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected( client.upload('/media/resize?token=' + assertToken, resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should upload file and create file', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media/resize?token=${token}`, resolve('test.png'), 'POST', { } ) ) testFiles.push(data.path) assert.ok(data) assert.ok(data.filename) assert.ok(data.filename.startsWith(currYear)) assert.ok(data.path) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve(`../../public/${data.path}`)), ]) assert.strictEqual(stats[0].size, stats[1].size) let img = await sharp(resolve(`../../public/${data.path}`)).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') }) t.test('should upload file and create multiple sizes for file', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media/resize?token=${token}`, resolve('test.png'), 'POST', { test1: { format: 'jpeg', resize: { width: 300, }, blur: 0.5, flatten: {r:0,g:0,b:0}, trim: 1, extend: { top: 10, left: 10, bottom: 10, right: 10, background: {r:0,g:0,b:0} }, jpeg: { quality: 80, mozjpeg: true, } }, test2: { format: 'png', resize: { width: 150, }, png: { compressionLevel: 9, } }, } ) ) testFiles.push(data.path) testFiles.push(data.test1.path) testFiles.push(data.test2.path) assert.ok(data) assert.ok(data.filename) assert.ok(data.filename.startsWith(currYear)) assert.ok(data.path) assert.ok(data.test1.filename) assert.ok(data.test1.filename.startsWith(currYear)) assert.ok(data.test1.path) assert.ok(data.test2.filename) assert.ok(data.test2.filename.startsWith(currYear)) assert.ok(data.test2.path) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve(`../../public/${data.path}`)), ]) assert.strictEqual(stats[0].size, stats[1].size) let img = await sharp(resolve(`../../public/${data.path}`)).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') img = await sharp(resolve(`../../public/${data.test1.path}`)).metadata() assert.strictEqual(img.width, 320) assert.strictEqual(img.height, 413) assert.strictEqual(img.format, 'jpeg') img = await sharp(resolve(`../../public/${data.test2.path}`)).metadata() assert.strictEqual(img.width, 150) assert.strictEqual(img.height, 175) assert.strictEqual(img.format, 'png') }) t.test('should upload file and support base64 output', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media/resize?token=${token}`, resolve('test.png'), 'POST', { outtest: { out: 'base64', format: 'jpeg', resize: { width: 10, }, jpeg: { quality: 80, mozjpeg: true, } }, } ) ) assert.ok(data) assert.ok(data.filename) assert.ok(data.path) assert.ok(data.outtest.base64) testFiles.push(data.path) let stats = await Promise.all([ fs.stat(resolve('test.png')), fs.stat(resolve(`../../public/${data.path}`)), ]) assert.strictEqual(stats[0].size, stats[1].size) let img = await sharp(resolve(`../../public/${data.path}`)).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') let bufferBase64 = Buffer.from(data.outtest.base64.slice(data.outtest.base64.indexOf(',')), 'base64') img = await sharp(bufferBase64).metadata() assert.strictEqual(img.width, 10) assert.strictEqual(img.height, 12) assert.strictEqual(img.format, 'jpeg') }) }) t.timeout(10000).describe('POST /media/resize/:filename', function temp() { let sourceFilename let sourcePath t.before(async function() { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.upload( `/media/resize?token=${token}`, resolve('test.png'), 'POST', { } ) ) testFiles.push(data.path) assert.ok(data) assert.ok(data.filename) assert.ok(data.filename.startsWith(currYear)) assert.ok(data.path) sourceFilename = data.filename sourcePath = data.path }) t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected( client.post(`/media/resize/${sourceFilename}`, {}) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected( client.post(`/media/resize/${sourceFilename}?token=${assertToken}`, {}) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should create multiple sizes for existing file', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.post( `/media/resize/${sourceFilename}?token=${token}`, { test1: { format: 'jpeg', resize: { width: 300, }, blur: 0.5, flatten: {r:0,g:0,b:0}, trim: 1, extend: { top: 10, left: 10, bottom: 10, right: 10, background: {r:0,g:0,b:0} }, jpeg: { quality: 80, mozjpeg: true, } }, test2: { format: 'png', resize: { width: 150, }, png: { compressionLevel: 9, } }, } ) ) testFiles.push(data.test1.path) testFiles.push(data.test2.path) assert.ok(data.test1.filename) assert.ok(data.test1.filename.startsWith(currYear)) assert.ok(data.test1.path) assert.ok(data.test2.filename) assert.ok(data.test2.filename.startsWith(currYear)) assert.ok(data.test2.path) let img = await sharp(resolve(`../../public/${sourcePath}`)).metadata() assert.strictEqual(img.width, 600) assert.strictEqual(img.height, 700) assert.strictEqual(img.format, 'png') img = await sharp(resolve(`../../public/${data.test1.path}`)).metadata() assert.strictEqual(img.width, 320) assert.strictEqual(img.height, 413) assert.strictEqual(img.format, 'jpeg') img = await sharp(resolve(`../../public/${data.test2.path}`)).metadata() assert.strictEqual(img.width, 150) assert.strictEqual(img.height, 175) assert.strictEqual(img.format, 'png') }) t.test('should base64 output of existing file', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await assert.isFulfilled( client.post( `/media/resize/${sourceFilename}?token=${token}`, { outtest: { out: 'base64', format: 'jpeg', resize: { width: 10, }, jpeg: { quality: 80, mozjpeg: true, } }, } ) ) assert.ok(data) assert.ok(data.outtest.base64) let bufferBase64 = Buffer.from(data.outtest.base64.slice(data.outtest.base64.indexOf(',')), 'base64') let img = await sharp(bufferBase64).metadata() assert.strictEqual(img.width, 10) assert.strictEqual(img.height, 12) assert.strictEqual(img.format, 'jpeg') }) }) t.timeout(10000).describe('DELETE /media/:filename', function temp() { t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected( client.del('/media/20220105_101610_test1.jpg', resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected( client.del('/media/20220105_101610_test1.jpg?token=' + assertToken, resolve('test.png') ) ) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should remove the file', async () => { let token = encode(null, { iss: 'existing' }, secret) let data = await client.upload( `/media/noprefix?token=${token}`, resolve('test.png') ) let filepath = data.path testFiles.push(filepath) let files = await client.get('/media/existing') let found = false for (let file of files) { if (file.filename === 'test.png') { found = true } } assert.ok(found) await assert.isFulfilled( fs.stat(resolve(`../../public/${filepath}`)) ) await assert.isFulfilled( client.del(`/media/test.png?token=${token}`) ) testFiles.splice(testFiles.length - 1) files = await client.get('/media/existing') found = false for (let file of files) { if (file.filename === 'test.png') { found = true } } assert.notOk(found) await assert.isRejected( fs.stat(resolve(`../../public/${filepath}`)) ) }) }) t.describe('GET /media', function() { t.test('should require authentication', async () => { resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) let err = await assert.isRejected(client.get('/media')) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Mm]issing/) assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Mm]issing/) }) t.test('should verify token correctly', async () => { const assertToken = 'asdf.asdf.asdf' resetLog() assert.strictEqual(log.error.callCount, 0) assert.strictEqual(log.warn.callCount, 0) assert.strictEqual(log.info.callCount, 0) let err = await assert.isRejected(client.get('/media?token=' + assertToken)) assert.strictEqual(err.status, 422) assert.match(err.message, /[Tt]oken/) assert.match(err.message, /[Ii]nvalid/) assert.strictEqual(log.error.callCount, 1) assert.strictEqual(log.warn.callCount, 2) assert.strictEqual(typeof(log.warn.firstCall[0]), 'string') assert.match(log.warn.firstCall[0], /[Tt]oken/) assert.match(log.warn.firstCall[0], /[Ii]nvalid/) assert.ok(log.error.lastCall[0] instanceof Error) assert.match(log.error.lastCall[1], new RegExp(assertToken)) }) t.test('should return list of files in specified folder', async () => { let token = encode(null, { iss: 'development' }, secret) let data = await client.get('/media?token=' + token) assert.ok(data.length) let found = false for (let file of data) { if (file.filename === '.gitkeep' && file.size === 0) { found = true break } } assert.ok(found) }) }) t.describe('GET /media/:site', function() { t.test('should give 404 on invalid sites', async () => { let err = await assert.isRejected(client.get('/media/development')) assert.strictEqual(err.status, 404) err = await assert.isRejected(client.get('/media/nonexisting')) assert.strictEqual(err.status, 404) err = await assert.isRejected(client.get('/media/blabla')) assert.strictEqual(err.status, 404) }) t.test('should otherwise return list of files for a public site', async () => { let files = await client.get('/media/existing') assert.strictEqual(files[0].filename, '20220105_101610_test1.jpg') assert.strictEqual(files[0].size, 15079) assert.strictEqual(files[1].filename, '20220105_101610_test2.png') assert.strictEqual(files[1].size, 31705) }) }) })