Many updates. Added ability to upload prefix-less files. Added ability to remove files.

Breaking: Renamed name to filename when listing files in folder.
Breaking: Fixed the schema response for /media/resize. It is now consistent with other upload methods.
dev
Jonatan Nilsson 2022-01-06 09:01:10 +00:00
parent 92338b94ca
commit 58553f3e34
8 changed files with 566 additions and 182 deletions

View File

@ -6,24 +6,28 @@ import config from '../config.mjs'
let lastDateString = ''
let incrementor = 1
export function uploadFile(ctx, siteName) {
export function uploadFile(ctx, siteName, noprefix = false) {
return new Promise((res, rej) => {
const date = new Date()
// Generate 'YYYYMMDD_HHMMSS_' prefix
let prefix = date
.toISOString()
.replace(/-/g, '')
.replace('T', '_')
.replace(/:/g, '')
.replace(/\..+/, '_')
let prefix = ''
// Append xx_ if same date is hit multiple times
if (prefix === lastDateString) {
prefix += incrementor.toString().padStart('2', '0') + '_'
incrementor++
} else {
lastDateString = prefix
if (!noprefix) {
// Generate 'YYYYMMDD_HHMMSS_' prefix
prefix = date
.toISOString()
.replace(/-/g, '')
.replace('T', '_')
.replace(/:/g, '')
.replace(/\..+/, '_')
// Append xx_ if same date is hit multiple times
if (prefix === lastDateString) {
prefix += incrementor.toString().padStart('2', '0') + '_'
incrementor++
} else {
lastDateString = prefix
}
}
var form = new formidable.IncomingForm()

View File

@ -25,7 +25,7 @@ export default class MediaRoutes {
return Promise.all(files.map(file => {
return fs.stat(`./public/${folder}/${file}`)
.then(function(stat) {
return { name: file, size: stat.size }
return { filename: file, size: stat.size }
})
}))
})
@ -40,7 +40,7 @@ export default class MediaRoutes {
filesCacheGet(site) {
let files = this.siteCache.get(site) || []
return files.sort((a, b) => {
return this.collator.compare(a.name, b.name)
return this.collator.compare(a.filename, b.filename)
})
}
@ -49,7 +49,29 @@ export default class MediaRoutes {
if (!arr) {
this.siteCache.set(site, arr = [])
}
arr.push({ name: filename, size: size })
let found = false
for (let file of arr) {
if (file.filename === filename) {
found = true
file.size = size
break
}
}
if (!found) {
arr.push({ filename: filename, size: size })
}
}
filesCacheRemove(site, filename) {
let arr = this.siteCache.get(site)
if (!arr) return
for (let i = 0; i < arr.length; i++) {
if (arr[i].filename === filename) {
arr.splice(i, 1)
break
}
}
}
async listFiles(ctx) {
@ -64,11 +86,11 @@ export default class MediaRoutes {
ctx.body = this.filesCacheGet(ctx.params.site)
}
async upload(ctx) {
async upload(ctx, noprefix = false) {
let site = await this.security.verifyToken(ctx)
ctx.state.site = site
let result = await this.formidable.uploadFile(ctx, ctx.state.site)
let result = await this.formidable.uploadFile(ctx, ctx.state.site, noprefix)
ctx.log.info(`Uploaded ${result.filename}`)
@ -81,15 +103,15 @@ export default class MediaRoutes {
}
}
uploadNoPrefix(ctx) {
return this.upload(ctx, true)
}
async resize(ctx) {
await this.upload(ctx)
this.security.verifyBody(ctx)
let out = {
original: ctx.body,
}
let keys = Object.keys(ctx.req.body)
await Promise.all(keys.map(key => {
@ -112,7 +134,7 @@ export default class MediaRoutes {
if (item.out === 'base64') {
let buffer = await sharp.toBuffer()
out[key] = {
ctx.body[key] = {
base64: `data:image/${item.format};base64,` + buffer.toString('base64'),
}
return
@ -122,7 +144,7 @@ export default class MediaRoutes {
let stat = await this.fs.stat(`./public/${ctx.state.site}/${target}`)
this.filesCacheAdd(ctx.state.site, target, stat.size)
out[key] = {
ctx.body[key] = {
filename: target,
path: `/${ctx.state.site}/${target}`,
}
@ -133,7 +155,18 @@ export default class MediaRoutes {
}
)
}))
}
ctx.body = out
async remove(ctx) {
let site = await this.security.verifyToken(ctx)
this.filesCacheRemove(site, ctx.params.filename)
await this.fs.unlink(`./public/${site}/${ctx.params.filename}`)
.catch(function(err) {
throw new HttpError(`Error removing ${site}/${ctx.params.filename}: ${err.message}`, 422)
})
ctx.status = 204
}
}

View File

@ -36,8 +36,8 @@ export function verifyBody(ctx) {
let keys = Object.keys(ctx.req.body)
for (let key of keys) {
if (key === 'original') {
throw new HttpError('Body item with name original is not allowed', 422)
if (key === 'filename' || key === 'path') {
throw new HttpError('Body item with name filename or path is not allowed', 422)
}
let item = ctx.req.body[key]

View File

@ -53,9 +53,11 @@ media.init().then(function() {}, function(err) {
log.error(err, 'Error initing media')
})
app.get('/media', [QueryHandler()], media.listFiles.bind(media))
app.get('/media/:site', [QueryHandler()], media.listPublicFiles.bind(media))
app.get('/media/:site', media.listPublicFiles.bind(media))
app.post('/media', [QueryHandler()], media.upload.bind(media))
app.post('/media/noprefix', [QueryHandler()], media.uploadNoPrefix.bind(media))
app.post('/media/resize', [QueryHandler()], media.resize.bind(media))
app.delete('/media/:filename', [QueryHandler()], media.remove.bind(media))
app.listen(config.get('server:port'), function(a,b) {
log.info(`Server listening at ${config.get('server:port')}`)

View File

@ -44,6 +44,7 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
})
res.on('end', function () {
if (!output) return resolve(null)
try {
output = JSON.parse(output)
} catch (e) {
@ -66,6 +67,10 @@ Client.prototype.get = function(url = '/') {
return this.customRequest('GET', url, null)
}
Client.prototype.del = function(url = '/', body = {}) {
return this.customRequest('DELETE', url, JSON.stringify(body))
}
const random = (length = 8) => {
// Declare all characters
let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';

View File

@ -14,6 +14,8 @@ function resolve(file) {
return path.resolve(path.join(__dirname, file))
}
const currYear = new Date().getFullYear().toString()
t.describe('Media (API)', () => {
const client = new Client()
const secret = 'asdf1234'
@ -23,6 +25,9 @@ t.describe('Media (API)', () => {
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() {
@ -83,12 +88,13 @@ t.describe('Media (API)', () => {
)
)
testFiles.push(data.path)
assert.ok(data)
assert.ok(data.filename)
assert.ok(data.filename.startsWith(currYear))
assert.ok(data.path)
testFiles.push(data.path)
let stats = await Promise.all([
fs.stat(resolve('test.png')),
fs.stat(resolve(`../../public/${data.path}`)),
@ -102,6 +108,83 @@ t.describe('Media (API)', () => {
})
})
t.timeout(10000).describe('POST /media/noprefix', function temp() {
t.test('should require authentication', async () => {
resetLog()
assert.strictEqual(server.log.error.callCount, 0)
assert.strictEqual(server.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(server.log.error.callCount, 0)
assert.strictEqual(server.log.warn.callCount, 2)
assert.strictEqual(typeof(server.log.warn.firstCall[0]), 'string')
assert.match(server.log.warn.firstCall[0], /[Tt]oken/)
assert.match(server.log.warn.firstCall[0], /[Mm]issing/)
})
t.test('should verify token correctly', async () => {
const assertToken = 'asdf.asdf.asdf'
resetLog()
assert.strictEqual(server.log.error.callCount, 0)
assert.strictEqual(server.log.warn.callCount, 0)
assert.strictEqual(server.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(server.log.error.callCount, 1)
assert.strictEqual(server.log.warn.callCount, 2)
assert.strictEqual(typeof(server.log.warn.firstCall[0]), 'string')
assert.match(server.log.warn.firstCall[0], /[Tt]oken/)
assert.match(server.log.warn.firstCall[0], /[Ii]nvalid/)
assert.ok(server.log.error.lastCall[0] instanceof Error)
assert.match(server.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.timeout(10000).describe('POST /media/resize', function temp() {
t.test('should require authentication', async () => {
resetLog()
@ -162,20 +245,20 @@ t.describe('Media (API)', () => {
)
)
assert.ok(data)
assert.ok(data.original)
assert.ok(data.original.filename)
assert.ok(data.original.path)
testFiles.push(data.path)
testFiles.push(data.original.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.original.path}`)),
fs.stat(resolve(`../../public/${data.path}`)),
])
assert.strictEqual(stats[0].size, stats[1].size)
let img = await sharp(resolve(`../../public/${data.original.path}`)).metadata()
let img = await sharp(resolve(`../../public/${data.path}`)).metadata()
assert.strictEqual(img.width, 600)
assert.strictEqual(img.height, 700)
assert.strictEqual(img.format, 'png')
@ -212,28 +295,30 @@ t.describe('Media (API)', () => {
}
)
)
assert.ok(data)
assert.ok(data.original)
assert.ok(data.original.filename)
assert.ok(data.original.path)
assert.ok(data.test1.filename)
assert.ok(data.test1.path)
assert.ok(data.test2.filename)
assert.ok(data.test2.path)
testFiles.push(data.original.path)
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.original.path}`)),
fs.stat(resolve(`../../public/${data.path}`)),
])
assert.strictEqual(stats[0].size, stats[1].size)
let img = await sharp(resolve(`../../public/${data.original.path}`)).metadata()
let img = await sharp(resolve(`../../public/${data.path}`)).metadata()
assert.strictEqual(img.width, 600)
assert.strictEqual(img.height, 700)
assert.strictEqual(img.format, 'png')
@ -248,7 +333,6 @@ t.describe('Media (API)', () => {
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)
@ -275,21 +359,20 @@ t.describe('Media (API)', () => {
)
assert.ok(data)
assert.ok(data.original)
assert.ok(data.original.filename)
assert.ok(data.original.path)
assert.ok(data.filename)
assert.ok(data.path)
assert.ok(data.outtest.base64)
testFiles.push(data.original.path)
testFiles.push(data.path)
let stats = await Promise.all([
fs.stat(resolve('test.png')),
fs.stat(resolve(`../../public/${data.original.path}`)),
fs.stat(resolve(`../../public/${data.path}`)),
])
assert.strictEqual(stats[0].size, stats[1].size)
let img = await sharp(resolve(`../../public/${data.original.path}`)).metadata()
let img = await sharp(resolve(`../../public/${data.path}`)).metadata()
assert.strictEqual(img.width, 600)
assert.strictEqual(img.height, 700)
assert.strictEqual(img.format, 'png')
@ -303,6 +386,101 @@ t.describe('Media (API)', () => {
})
})
t.timeout(10000).describe('DELETE /media/:filename', function temp() {
t.test('should require authentication', async () => {
resetLog()
assert.strictEqual(server.log.error.callCount, 0)
assert.strictEqual(server.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(server.log.error.callCount, 0)
assert.strictEqual(server.log.warn.callCount, 2)
assert.strictEqual(typeof(server.log.warn.firstCall[0]), 'string')
assert.match(server.log.warn.firstCall[0], /[Tt]oken/)
assert.match(server.log.warn.firstCall[0], /[Mm]issing/)
})
t.test('should verify token correctly', async () => {
const assertToken = 'asdf.asdf.asdf'
resetLog()
assert.strictEqual(server.log.error.callCount, 0)
assert.strictEqual(server.log.warn.callCount, 0)
assert.strictEqual(server.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(server.log.error.callCount, 1)
assert.strictEqual(server.log.warn.callCount, 2)
assert.strictEqual(typeof(server.log.warn.firstCall[0]), 'string')
assert.match(server.log.warn.firstCall[0], /[Tt]oken/)
assert.match(server.log.warn.firstCall[0], /[Ii]nvalid/)
assert.ok(server.log.error.lastCall[0] instanceof Error)
assert.match(server.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()
@ -351,7 +529,7 @@ t.describe('Media (API)', () => {
assert.ok(data.length)
let found = false
for (let file of data) {
if (file.name === '.gitkeep' && file.size === 0) {
if (file.filename === '.gitkeep' && file.size === 0) {
found = true
break
}
@ -377,9 +555,9 @@ t.describe('Media (API)', () => {
t.test('should otherwise return list of files for a public site', async () => {
let files = await client.get('/media/existing')
assert.strictEqual(files[0].name, '20220105_101610_test1.jpg')
assert.strictEqual(files[0].filename, '20220105_101610_test1.jpg')
assert.strictEqual(files[0].size, 15079)
assert.strictEqual(files[1].name, '20220105_101610_test2.png')
assert.strictEqual(files[1].filename, '20220105_101610_test2.png')
assert.strictEqual(files[1].size, 31705)
})
})

View File

@ -1,9 +1,23 @@
import fs from 'fs/promises'
import { Eltro as t, assert, stub } from 'eltro'
import { createContext } from '../helper.server.mjs'
import MediaRoutes from '../../api/media/routes.mjs'
import { HttpError } from '../../api/error.mjs'
t.before(function() {
return Promise.all([
fs.readdir('./public/development').then(files => {
return Promise.all(files.map(function(file) {
if (file !== '.gitkeep') {
return fs.unlink(`./public/development/${file}`).catch(function() {})
}
return Promise.resolve()
}))
}),
])
})
t.describe('#filesCacheGet', function() {
t.test('should return all files in public folder', async function() {
const routes = new MediaRoutes()
@ -21,11 +35,11 @@ t.describe('#filesCacheGet', function() {
assert.strictEqual(routes.filesCacheGet('existing').length, 2)
assert.strictEqual(routes.filesCacheGet('nonexisting').length, 0)
assert.strictEqual(routes.filesCacheGet('development')[0].name, '.gitkeep')
assert.strictEqual(routes.filesCacheGet('development')[0].filename, '.gitkeep')
assert.strictEqual(routes.filesCacheGet('development')[0].size, 0)
assert.strictEqual(routes.filesCacheGet('existing')[0].name, '20220105_101610_test1.jpg')
assert.strictEqual(routes.filesCacheGet('existing')[0].filename, '20220105_101610_test1.jpg')
assert.strictEqual(routes.filesCacheGet('existing')[0].size, 15079)
assert.strictEqual(routes.filesCacheGet('existing')[1].name, '20220105_101610_test2.png')
assert.strictEqual(routes.filesCacheGet('existing')[1].filename, '20220105_101610_test2.png')
assert.strictEqual(routes.filesCacheGet('existing')[1].size, 31705)
})
@ -36,19 +50,19 @@ t.describe('#filesCacheGet', function() {
let files = routes.filesCacheGet('existing')
assert.strictEqual(files[0].name, '20220105_101610_test1.jpg')
assert.strictEqual(files[0].filename, '20220105_101610_test1.jpg')
assert.strictEqual(files[0].size, 15079)
assert.strictEqual(files[1].name, '20220105_101610_test2.png')
assert.strictEqual(files[1].filename, '20220105_101610_test2.png')
assert.strictEqual(files[1].size, 31705)
routes.siteCache.get('existing').push({ name: '0000.png', size: 0 })
routes.siteCache.get('existing').push({ filename: '0000.png', size: 0 })
files = routes.filesCacheGet('existing')
assert.strictEqual(files[0].name, '0000.png')
assert.strictEqual(files[0].filename, '0000.png')
assert.strictEqual(files[0].size, 0)
assert.strictEqual(files[1].name, '20220105_101610_test1.jpg')
assert.strictEqual(files[1].filename, '20220105_101610_test1.jpg')
assert.strictEqual(files[1].size, 15079)
assert.strictEqual(files[2].name, '20220105_101610_test2.png')
assert.strictEqual(files[2].filename, '20220105_101610_test2.png')
assert.strictEqual(files[2].size, 31705)
})
})
@ -160,19 +174,83 @@ t.describe('#filesCacheAdd', function() {
routes.filesCacheAdd('nonexisting', assertName, assertSize)
assert.strictEqual(routes.filesCacheGet('nonexisting').length, 1)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].name, assertName)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].filename, assertName)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].size, assertSize)
})
t.test('should not add but update size if file already exists', function() {
const routes = new MediaRoutes()
const assertName = 'asdf.png'
const assertSize = 1234
const assertNewSize = 5678
assert.ok(routes.filesCacheGet('nonexisting'))
assert.ok(Array.isArray(routes.filesCacheGet('nonexisting')))
assert.strictEqual(routes.filesCacheGet('nonexisting').length, 0)
routes.filesCacheAdd('nonexisting', assertName, assertSize)
assert.strictEqual(routes.filesCacheGet('nonexisting').length, 1)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].filename, assertName)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].size, assertSize)
routes.filesCacheAdd('nonexisting', assertName, assertNewSize)
assert.strictEqual(routes.filesCacheGet('nonexisting').length, 1)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].filename, assertName)
assert.strictEqual(routes.filesCacheGet('nonexisting')[0].size, assertNewSize)
})
})
t.describe('#upload', function() {
t.describe('#filesCacheRemove', function() {
t.test('should default not do anything if nothing is found', async function() {
const routes = new MediaRoutes()
await routes.init()
assert.ok(routes.filesCacheGet('existing'))
assert.ok(Array.isArray(routes.filesCacheGet('existing')))
assert.strictEqual(routes.filesCacheGet('existing').length, 2)
routes.filesCacheRemove('nonexisting', 'bla.text')
routes.filesCacheRemove('nonexisting', 'herp.derp')
routes.filesCacheRemove('existing', 'bla.text')
routes.filesCacheRemove('existing', 'herp.derp')
assert.ok(routes.filesCacheGet('existing'))
assert.ok(Array.isArray(routes.filesCacheGet('existing')))
assert.strictEqual(routes.filesCacheGet('existing').length, 2)
})
t.test('should otherwise remove entries that match filename', async function() {
const routes = new MediaRoutes()
await routes.init()
assert.ok(routes.filesCacheGet('existing'))
assert.ok(Array.isArray(routes.filesCacheGet('existing')))
assert.strictEqual(routes.filesCacheGet('existing').length, 2)
routes.filesCacheRemove('existing', '20220105_101610_test1.jpg')
assert.ok(routes.filesCacheGet('existing'))
assert.ok(Array.isArray(routes.filesCacheGet('existing')))
assert.strictEqual(routes.filesCacheGet('existing').length, 1)
assert.strictEqual(routes.filesCacheGet('existing')[0].filename, '20220105_101610_test2.png')
})
})
t.describe('#uploadNoPrefix', function() {
const stubVerify = stub()
const stubUpload = stub()
const stubStat = stub()
const routes = new MediaRoutes({
security: { verifyToken: stubVerify },
formidable: { uploadFile: stubUpload },
security: {
verifyToken: stubVerify,
verifyBody: stub(),
},
formidable: { uploadFile: stubUpload, },
fs: { stat: stubStat },
})
@ -181,21 +259,7 @@ t.describe('#upload', function() {
stubUpload.reset()
stubStat.reset()
}
t.test('should call security correctly', async function() {
reset()
let ctx = createContext()
const assertError = new Error('temp')
stubVerify.rejects(assertError)
let err = await assert.isRejected(routes.upload(ctx))
assert.ok(stubVerify.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubVerify.firstCall[0], ctx)
})
t.test('should call upload correctly', async function() {
reset()
@ -205,36 +269,13 @@ t.describe('#upload', function() {
stubVerify.resolves(assertSiteName)
stubUpload.rejects(assertError)
let err = await assert.isRejected(routes.upload(ctx))
let err = await assert.isRejected(routes.uploadNoPrefix(ctx))
assert.ok(stubUpload.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubUpload.firstCall[0], ctx)
assert.strictEqual(stubUpload.firstCall[1], assertSiteName)
})
t.test('should otherwise set context status to 204 and file in result', async function() {
reset()
let ctx = createContext()
const assertSize = 1241412
const assertFilename = 'asdfsafd'
const assertSite = 'mario'
stubVerify.resolves(assertSite)
stubUpload.resolves({ filename: assertFilename })
stubStat.resolves({ size: assertSize })
await routes.upload(ctx)
assert.strictEqual(ctx.body.filename, assertFilename)
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}`)
assert.ok(stubStat.called)
assert.strictEqual(stubStat.firstCall[0], `./public/${assertSite}/${assertFilename}`)
let filesFromCache = routes.filesCacheGet(assertSite)
assert.strictEqual(filesFromCache.length, 1)
assert.strictEqual(filesFromCache[0].name, assertFilename)
assert.strictEqual(filesFromCache[0].size, assertSize)
assert.strictEqual(stubUpload.firstCall[2], true)
})
})
@ -292,37 +333,6 @@ t.describe('#resize', function() {
routes.siteCache.clear()
}
t.test('should call security verifyToken correctly', async function() {
reset()
let ctx = createContext({ req: { body: { }}})
const assertError = new Error('temp')
stubVerifyToken.rejects(assertError)
let err = await assert.isRejected(routes.resize(ctx))
assert.ok(stubVerifyToken.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubVerifyToken.firstCall[0], ctx)
})
t.test('should call upload correctly', async function() {
reset()
let ctx = createContext({ req: { body: { }}})
const assertSiteName = 'benshapiro'
const assertError = new Error('hello')
stubVerifyToken.resolves(assertSiteName)
stubUpload.rejects(assertError)
let err = await assert.isRejected(routes.resize(ctx))
assert.strictEqual(err, assertError)
assert.ok(stubUpload.called)
assert.strictEqual(stubUpload.firstCall[0], ctx)
assert.strictEqual(stubUpload.firstCall[1], assertSiteName)
})
t.test('should call security verifyBody correctly', async function() {
reset()
@ -338,22 +348,6 @@ t.describe('#resize', function() {
assert.strictEqual(stubVerifyBody.firstCall[0], ctx)
})
t.test('should otherwise return original in result', async function() {
reset()
let ctx = createContext({ req: { body: { }}})
const assertFilename = 'asdfsafd.png'
const assertSite = 'mario'
stubVerifyToken.resolves(assertSite)
stubUpload.resolves({ filename: assertFilename })
await routes.resize(ctx)
assert.notOk(stubSharp.called)
assert.strictEqual(ctx.body.original.filename, assertFilename)
assert.strictEqual(ctx.body.original.path, `/${assertSite}/${assertFilename}`)
})
t.test('should call sharp correctly if items are specified', async function() {
reset()
const assertKey = 'asdf'
@ -381,8 +375,8 @@ t.describe('#resize', function() {
assert.match(stubSharpToFile.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}_${assertKey}\.jpg`))
assert.notOk(stubSharpPng.called)
assert.notOk(stubSharpResize.called)
assert.strictEqual(ctx.body.original.filename, assertFilename + '.png')
assert.strictEqual(ctx.body.original.path, `/${assertSite}/${assertFilename}.png`)
assert.strictEqual(ctx.body.filename, assertFilename + '.png')
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}.png`)
assert.strictEqual(ctx.body[assertKey].filename, `${assertFilename}_${assertKey}\.jpg`)
assert.strictEqual(ctx.body[assertKey].path, `/${assertSite}/${assertFilename}_${assertKey}.jpg`)
})
@ -423,16 +417,16 @@ t.describe('#resize', function() {
assert.notOk(stubSharpJpeg.called)
assert.ok(stubSharpResize.called)
assert.strictEqual(stubSharpResize.firstCall[0], assertResize)
assert.strictEqual(ctx.body.original.filename, assertFilename + '.png')
assert.strictEqual(ctx.body.original.path, `/${assertSite}/${assertFilename}.png`)
assert.strictEqual(ctx.body.filename, assertFilename + '.png')
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}.png`)
assert.strictEqual(ctx.body[assertKey].filename, `${assertFilename}_${assertKey}\.png`)
assert.strictEqual(ctx.body[assertKey].path, `/${assertSite}/${assertFilename}_${assertKey}.png`)
let filesFromCache = routes.filesCacheGet(assertSite)
assert.strictEqual(filesFromCache.length, 2)
assert.strictEqual(filesFromCache[0].name, `${assertFilename}_${assertKey}\.png`)
assert.strictEqual(filesFromCache[0].filename, `${assertFilename}_${assertKey}\.png`)
assert.strictEqual(filesFromCache[0].size, 20)
assert.strictEqual(filesFromCache[1].name, assertFilename + '.png')
assert.strictEqual(filesFromCache[1].filename, assertFilename + '.png')
assert.strictEqual(filesFromCache[1].size, 10)
})
@ -513,6 +507,170 @@ t.describe('#resize', function() {
let filesFromCache = routes.filesCacheGet(assertSite)
assert.strictEqual(filesFromCache.length, 1)
assert.strictEqual(filesFromCache[0].name, assertFilename)
assert.strictEqual(filesFromCache[0].filename, assertFilename)
})
})
let basicUploadTestRoutes = [
'upload',
'resize',
'uploadNoPrefix'
]
basicUploadTestRoutes.forEach(function(name) {
t.describe(`#${name}() Base`, function() {
const stubVerify = stub()
const stubUpload = stub()
const stubStat = stub()
const routes = new MediaRoutes({
security: {
verifyToken: stubVerify,
verifyBody: stub(),
},
formidable: { uploadFile: stubUpload, },
fs: { stat: stubStat },
})
function reset() {
stubVerify.reset()
stubUpload.reset()
stubStat.reset()
}
t.test('should call security correctly', async function() {
reset()
let ctx = createContext({ req: { body: { } } })
const assertError = new Error('temp')
stubVerify.rejects(assertError)
let err = await assert.isRejected(routes[name](ctx))
assert.ok(stubVerify.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubVerify.firstCall[0], ctx)
})
t.test('should call upload correctly', async function() {
reset()
let ctx = createContext({ req: { body: { } } })
const assertSiteName = 'benshapiro'
const assertError = new Error('hello')
stubVerify.resolves(assertSiteName)
stubUpload.rejects(assertError)
let err = await assert.isRejected(routes[name](ctx))
assert.ok(stubUpload.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubUpload.firstCall[0], ctx)
assert.strictEqual(stubUpload.firstCall[1], assertSiteName)
})
t.test('should otherwise return the file in result', async function() {
reset()
let ctx = createContext({ req: { body: { } } })
const assertSize = 1241412
const assertFilename = 'asdfsafd'
const assertSite = 'mario'
stubVerify.resolves(assertSite)
stubUpload.resolves({ filename: assertFilename })
stubStat.resolves({ size: assertSize })
await routes[name](ctx)
assert.strictEqual(ctx.body.filename, assertFilename)
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}`)
assert.ok(stubStat.called)
assert.strictEqual(stubStat.firstCall[0], `./public/${assertSite}/${assertFilename}`)
let filesFromCache = routes.filesCacheGet(assertSite)
assert.strictEqual(filesFromCache.length, 1)
assert.strictEqual(filesFromCache[0].filename, assertFilename)
assert.strictEqual(filesFromCache[0].size, assertSize)
})
})
})
t.describe('#remove()', function() {
const stubVerify = stub()
const stubUnlink = stub()
const stubCacheRemove = stub()
const routes = new MediaRoutes({
security: {
verifyToken: stubVerify,
},
fs: { unlink: stubUnlink },
})
routes.filesCacheRemove = stubCacheRemove
function reset() {
stubVerify.reset()
stubUnlink.reset()
stubCacheRemove.reset()
stubUnlink.resolves(null)
}
t.test('should call security correctly', async function() {
reset()
let ctx = createContext({ req: { body: { } } })
const assertError = new Error('temp')
stubVerify.rejects(assertError)
let err = await assert.isRejected(routes.remove(ctx))
assert.ok(stubVerify.called)
assert.strictEqual(err, assertError)
assert.strictEqual(stubVerify.firstCall[0], ctx)
})
t.test('should call unlink correctly', async function() {
const assertSiteName = 'benshapiro'
const assertFilename = 'somefilename.png'
const assertErrorMessage = 'Noriyasu Agematsu Tensei'
const assertError = new Error(assertErrorMessage)
reset()
let ctx = createContext({ req: { body: { } } })
ctx.params.filename = assertFilename
stubVerify.resolves(assertSiteName)
stubUnlink.rejects(assertError)
let err = await assert.isRejected(routes.remove(ctx))
assert.ok(stubUnlink.called)
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, new RegExp(assertSiteName))
assert.match(err.message, new RegExp(assertFilename))
assert.match(err.message, new RegExp(assertErrorMessage))
assert.strictEqual(stubUnlink.firstCall[0], `./public/${assertSiteName}/${assertFilename}`)
})
t.test('should otherwise return 204 status and remove from array', async function() {
const assertSiteName = 'benshapiro'
const assertFilename = 'somefilename.png'
reset()
let ctx = createContext({ req: { body: { } } })
ctx.params.filename = assertFilename
stubVerify.resolves(assertSiteName)
await routes.remove(ctx)
assert.strictEqual(ctx.status, 204)
assert.ok(stubCacheRemove.called)
assert.strictEqual(stubCacheRemove.firstCall[0], assertSiteName)
assert.strictEqual(stubCacheRemove.firstCall[1], assertFilename)
})
})

View File

@ -173,21 +173,25 @@ t.describe('#verifyBody()', function() {
})
})
t.test('should fail if an item has the name original', function() {
let ctx = createContext({ req: { body: {
original: {}
} } })
let testInvalidNames = ['filename', 'path']
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /name/i)
assert.match(err.message, /original/i)
assert.match(err.message, /allowed/i)
return true
}, 'should fail if body item has the name original')
testInvalidNames.forEach(function(invalidName) {
t.test(`should fail if an item has the name ${invalidName}`, function() {
let ctx = createContext({ req: { body: {
[invalidName]: {}
} } })
assert.throws(function() { verifyBody(ctx) }, function(err) {
assert.ok(err instanceof HttpError)
assert.ok(err instanceof Error)
assert.strictEqual(err.status, 422)
assert.match(err.message, /body/i)
assert.match(err.message, /name/i)
assert.match(err.message, new RegExp(invalidName))
assert.match(err.message, /allowed/i)
return true
}, 'should fail if body item has the name original')
})
})
t.test('should require format string present in item', function() {