Finished unit test, refactored to use flaska instead of koa
This commit is contained in:
parent
2b53b48a5d
commit
11c1133696
19 changed files with 422 additions and 211 deletions
|
@ -1,11 +1,18 @@
|
|||
|
||||
export class HttpError extends Error {
|
||||
constructor(message, status = 500) {
|
||||
super(message)
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
export async function errorMiddleware(ctx, next) {
|
||||
try {
|
||||
await next()
|
||||
} catch (e) {
|
||||
ctx.status = 422
|
||||
ctx.status = e.status || 500
|
||||
ctx.body = {
|
||||
status: 422,
|
||||
status: ctx.status,
|
||||
message: e.message,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,17 +2,24 @@ import crypto from 'crypto'
|
|||
import * as base64UrlSafe from './base64urlsafe.mjs'
|
||||
import defaults from '../defaults.mjs'
|
||||
|
||||
export default function encode(header, orgBody, privateKeyPassword = null, disableAutofill = false) {
|
||||
let body = defaults(orgBody)
|
||||
export default function encode(orgHeader, orgBody, privateKeyPassword = null, disableAutofill = false) {
|
||||
if (
|
||||
typeof header !== 'object' ||
|
||||
Array.isArray(header) ||
|
||||
typeof body !== 'object' ||
|
||||
Array.isArray(body)
|
||||
typeof orgHeader !== 'object' ||
|
||||
typeof orgBody !== 'object'
|
||||
) {
|
||||
throw new Error('both header and body should be of type object')
|
||||
}
|
||||
|
||||
let header = defaults(orgHeader)
|
||||
let body = defaults(orgBody)
|
||||
|
||||
if (!header.alg) {
|
||||
header.alg = 'HS256'
|
||||
}
|
||||
if (!header.typ) {
|
||||
header.typ = 'JWT'
|
||||
}
|
||||
|
||||
let hmacAlgo = null
|
||||
switch (header.alg) {
|
||||
case 'HS256':
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
import { verifyToken } from './security.mjs'
|
||||
import { uploadFile } from './multer.mjs'
|
||||
import * as security from './security.mjs'
|
||||
import * as multer from './multer.mjs'
|
||||
|
||||
export async function upload(ctx) {
|
||||
let site = await verifyToken(ctx)
|
||||
export default class MediaRoutes {
|
||||
constructor(opts = {}) {
|
||||
Object.assign(this, {
|
||||
security: opts.security || security,
|
||||
multer: opts.multer || multer,
|
||||
})
|
||||
}
|
||||
|
||||
let result = await uploadFile(ctx, site)
|
||||
async upload(ctx) {
|
||||
let site = await this.security.verifyToken(ctx)
|
||||
|
||||
let result = await this.multer.uploadFile(ctx, site)
|
||||
|
||||
ctx.body = {
|
||||
filename: result.filename,
|
||||
path: `/${site}/${result.filename}`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
// import * as jwt from '../jwt.mjs'
|
||||
import { HttpError } from '../error.mjs'
|
||||
import decode from '../jwt/decode.mjs'
|
||||
import config from '../config.mjs'
|
||||
|
||||
export function verifyToken(ctx) {
|
||||
if (!ctx.query.token) {
|
||||
throw new Error('Token is missing in query')
|
||||
let token = ctx.query.get('token')
|
||||
if (!token) {
|
||||
throw new HttpError('Token is missing in query', 422)
|
||||
}
|
||||
|
||||
let decoded = decode(ctx.query.token, config.get('sites'), [])
|
||||
|
||||
try {
|
||||
let decoded = decode(token, config.get('sites'), [])
|
||||
return decoded.iss
|
||||
} catch (err) {
|
||||
ctx.log.error(err, 'Error decoding token: ' + token)
|
||||
throw new HttpError('Token was invalid', 422)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
import Router from 'koa-router'
|
||||
|
||||
import * as test from './test/routes.mjs'
|
||||
import * as media from './media/routes.mjs'
|
||||
|
||||
const router = new Router()
|
||||
|
||||
router.get('/', test.testStatic)
|
||||
router.get('/error', test.testError)
|
||||
router.post('/media', media.upload)
|
||||
|
||||
export default router
|
|
@ -1,7 +1,14 @@
|
|||
import { performance } from 'perf_hooks'
|
||||
import Koa from 'koa-lite'
|
||||
import { Flaska, QueryHandler } from 'flaska'
|
||||
|
||||
import TestRoutes from './test/routes.mjs'
|
||||
import MediaRoutes from './media/routes.mjs'
|
||||
|
||||
import config from './config.mjs'
|
||||
import log from './log.mjs'
|
||||
|
||||
/*
|
||||
import router from './router.mjs'
|
||||
import { errorMiddleware } from './error.mjs'
|
||||
|
||||
|
@ -15,4 +22,51 @@ const server = app.listen(config.get('server:port'), function(a,b) {
|
|||
log.info(`Server listening at ${config.get('server:port')}`)
|
||||
})
|
||||
|
||||
export default server
|
||||
export default server */
|
||||
|
||||
const app = new Flaska({
|
||||
log: log,
|
||||
})
|
||||
|
||||
app.before(function(ctx) {
|
||||
ctx.__started = performance.now()
|
||||
})
|
||||
|
||||
app.after(function(ctx) {
|
||||
let ended = performance.now() - ctx.__started
|
||||
let logger = ctx.log.info
|
||||
if (ctx.status >= 400) {
|
||||
logger = ctx.log.warn
|
||||
}
|
||||
logger({
|
||||
path: ctx.url,
|
||||
status: ctx.status,
|
||||
ms: Math.round(ended),
|
||||
}, 'Request finished')
|
||||
})
|
||||
|
||||
app.onerror(function(err, ctx) {
|
||||
if (err.status && err.status >= 400 && err.status < 500) {
|
||||
ctx.log.warn(err.message)
|
||||
} else {
|
||||
ctx.log.error(err)
|
||||
}
|
||||
ctx.status = err.status || 500
|
||||
ctx.body = {
|
||||
status: ctx.status,
|
||||
message: err.message,
|
||||
}
|
||||
})
|
||||
|
||||
const test = new TestRoutes()
|
||||
app.get('/', test.static.bind(test))
|
||||
app.get('/error', test.error.bind(test))
|
||||
|
||||
const media = new MediaRoutes()
|
||||
app.post('/media', [QueryHandler()], media.upload.bind(media))
|
||||
|
||||
app.listen(config.get('server:port'), function(a,b) {
|
||||
log.info(`Server listening at ${config.get('server:port')}`)
|
||||
})
|
||||
|
||||
export default app
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import config from '../config.mjs'
|
||||
|
||||
export async function testStatic(ctx) {
|
||||
export default class TestRoutes {
|
||||
constructor(opts = {}) {
|
||||
Object.assign(this, { })
|
||||
}
|
||||
|
||||
static(ctx) {
|
||||
ctx.body = {
|
||||
name: config.get('name'),
|
||||
version: config.get('version'),
|
||||
|
@ -8,6 +13,7 @@ export async function testStatic(ctx) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function testError(ctx) {
|
||||
error(ctx) {
|
||||
throw new Error('This is a test')
|
||||
}
|
||||
}
|
||||
|
|
15
config/config.test.json
Normal file
15
config/config.test.json
Normal file
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"bunyan": {
|
||||
"name": "storage-upload-test",
|
||||
"streams": [{
|
||||
"stream": "process.stdout",
|
||||
"level": "error"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sites": {
|
||||
"development": {
|
||||
"default@HS256": "asdf1234"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,8 @@
|
|||
"scripts": {
|
||||
"dev": "nodemon index.js",
|
||||
"start": "node --experimental-modules api/server.mjs",
|
||||
"test": "eltro test/**/*.test.mjs -r dot"
|
||||
"test": "set NODE_ENV=test&& eltro test/**/*.test.mjs -r dot",
|
||||
"test:linux": "NODE_ENV=test eltro test/**/*.test.mjs -r dot"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -20,12 +21,11 @@
|
|||
"homepage": "https://github.com/nfp-projects/storage-upload#readme",
|
||||
"dependencies": {
|
||||
"bunyan-lite": "^1.1.1",
|
||||
"koa-lite": "^2.10.1",
|
||||
"koa-router": "^7.2.1",
|
||||
"flaska": "^0.9.5",
|
||||
"multer": "^1.3.0",
|
||||
"nconf-lite": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eltro": "^1.1.0"
|
||||
"eltro": "^1.2.2"
|
||||
}
|
||||
}
|
||||
|
|
1
test.json
Normal file
1
test.json
Normal file
|
@ -0,0 +1 @@
|
|||
{"name":"storage-upload","hostname":"JonatanPC","pid":26596,"level":30,"path":"/","status":200,"ms":4,"msg":"Request finished","time":"2021-10-10T23:55:12.390Z","v":0}
|
|
@ -1,4 +1,6 @@
|
|||
import http from 'http'
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import { URL } from 'url'
|
||||
import defaults from '../api/defaults.mjs'
|
||||
import config from '../api/config.mjs'
|
||||
|
@ -50,6 +52,7 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
|
|||
if (output.status) {
|
||||
let err = new Error(`Request failed [${output.status}]: ${output.message}`)
|
||||
err.body = output
|
||||
err.status = output.status
|
||||
return reject(err)
|
||||
}
|
||||
resolve(output)
|
||||
|
@ -59,8 +62,37 @@ Client.prototype.customRequest = function(method = 'GET', path, body, options) {
|
|||
})
|
||||
}
|
||||
|
||||
Client.prototype.get = function(path = '/') {
|
||||
return this.customRequest('GET', path, null)
|
||||
Client.prototype.get = function(url = '/') {
|
||||
return this.customRequest('GET', url, null)
|
||||
}
|
||||
|
||||
Client.prototype.upload = function(url, file, method = 'POST') {
|
||||
return fs.readFile(file).then(data => {
|
||||
const crlf = '\r\n'
|
||||
const filename = path.basename(file)
|
||||
const boundary = `---------${Math.random().toString(16)}`
|
||||
const headers = [
|
||||
`Content-Disposition: form-data; name="file"; filename="${filename}"` + crlf,
|
||||
]
|
||||
const multipartBody = Buffer.concat([
|
||||
Buffer.from(
|
||||
`${crlf}--${boundary}${crlf}` +
|
||||
headers.join('') + crlf
|
||||
),
|
||||
data,
|
||||
Buffer.from(
|
||||
`${crlf}--${boundary}--`
|
||||
),
|
||||
])
|
||||
|
||||
return this.customRequest(method, url, multipartBody, {
|
||||
timeout: 5000,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data; boundary=' + boundary,
|
||||
'Content-Length': multipartBody.length,
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -1,17 +1,38 @@
|
|||
// import _ from 'lodash'
|
||||
// import sinon from 'sinon'
|
||||
import { stub } from 'eltro'
|
||||
import Client from './helper.client.mjs'
|
||||
import defaults from '../api/defaults.mjs'
|
||||
import '../api/server.mjs'
|
||||
import serv from '../api/server.mjs'
|
||||
|
||||
serv.log = {
|
||||
log: stub(),
|
||||
warn: stub(),
|
||||
info: stub(),
|
||||
error: stub(),
|
||||
}
|
||||
|
||||
export const server = serv
|
||||
|
||||
export function createClient() {
|
||||
return new Client()
|
||||
}
|
||||
|
||||
export function resetLog() {
|
||||
serv.log.log.reset()
|
||||
serv.log.info.reset()
|
||||
serv.log.warn.reset()
|
||||
serv.log.error.reset()
|
||||
}
|
||||
|
||||
export function createContext(opts) {
|
||||
return defaults(opts, {
|
||||
query: { },
|
||||
query: new Map(),
|
||||
req: { },
|
||||
res: { },
|
||||
log: {
|
||||
log: stub(),
|
||||
warn: stub(),
|
||||
info: stub(),
|
||||
error: stub(),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import encode from '../../api/jwt/encode.mjs'
|
||||
import decode from '../../api/jwt/decode.mjs'
|
||||
|
||||
t.describe('encode', function() {
|
||||
t.test('should faile with invalid header and body', function() {
|
||||
|
@ -10,12 +11,18 @@ t.describe('encode', function() {
|
|||
/both header and body should be of type object/
|
||||
)
|
||||
})
|
||||
t.test('should faile with empty header and body', function() {
|
||||
t.test('should faile with invalid alg type', function() {
|
||||
assert.throws(
|
||||
function() {
|
||||
encode({}, {})
|
||||
encode({ alg: 'asdf' }, {})
|
||||
},
|
||||
/Only alg HS256, HS384 and HS512 are supported/
|
||||
)
|
||||
})
|
||||
t.test('should have default header options', function() {
|
||||
const assertTest = '1234'
|
||||
let token = encode(null, { iss: 'test', test: assertTest }, 'bla')
|
||||
let decoded = decode(token, { 'test': { 'default@HS256': 'bla' } }, [])
|
||||
assert.strictEqual(decoded.test, assertTest)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
|
||||
import createClient from '../helper.client.mjs'
|
||||
import config from '../../api/config.mjs'
|
||||
import jwt from '../../api/jwt.mjs'
|
||||
|
||||
let __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
console.log(__dirname)
|
||||
|
||||
t.describe('Media (API)', () => {
|
||||
let testFile
|
||||
let client = createClient()
|
||||
|
||||
t.before(() => {
|
||||
config.set('sites', {
|
||||
development: 'hello-world'
|
||||
})
|
||||
})
|
||||
|
||||
t.after(done => {
|
||||
if (testFile) {
|
||||
// path.resolve(path.join(__dirname, 'fixtures', file))
|
||||
return fs.unlink( appRoot.resolve(`/public${testFile}`), done)
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
t.describe('POST /media', function temp() {
|
||||
this.timeout(10000)
|
||||
|
||||
t.test('should require authentication', async () => {
|
||||
let err = await assert.isRejected(
|
||||
client.sendFileAsync('/media',
|
||||
appRoot.resolve('/test/media/test.png')))
|
||||
|
||||
assert.strictEqual(err.status, 422)
|
||||
assert.match(err.message, /[Tt]oken/)
|
||||
})
|
||||
|
||||
t.test('should upload file and create file', async () => {
|
||||
let token = jwt.sign({ site: 'development' }, 'hello-world')
|
||||
|
||||
let data = await assert.isFulfilled(
|
||||
client.sendFileAsync(
|
||||
`/media?token=${token}`,
|
||||
appRoot.resolve('/test/media/test.png')
|
||||
)
|
||||
)
|
||||
|
||||
assert.ok(data)
|
||||
assert.ok(data.filename)
|
||||
assert.ok(data.path)
|
||||
|
||||
testFile = data.path
|
||||
})
|
||||
})
|
||||
})
|
94
test/media/api.test.mjs
Normal file
94
test/media/api.test.mjs
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'url'
|
||||
import path from 'path'
|
||||
|
||||
import { server, resetLog } from '../helper.server.mjs'
|
||||
import Client from '../helper.client.mjs'
|
||||
import config from '../../api/config.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))
|
||||
}
|
||||
|
||||
t.describe('Media (API)', () => {
|
||||
const client = new Client()
|
||||
const secret = 'asdf1234'
|
||||
let testFile
|
||||
|
||||
t.after(function(done) {
|
||||
if (testFile) {
|
||||
return fs.unlink(resolve(`../../public/${testFile}`), done)
|
||||
}
|
||||
done()
|
||||
})
|
||||
|
||||
t.timeout(10000).describe('POST /media', 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',
|
||||
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?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 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')
|
||||
)
|
||||
)
|
||||
|
||||
assert.ok(data)
|
||||
assert.ok(data.filename)
|
||||
assert.ok(data.path)
|
||||
|
||||
testFile = data.path
|
||||
})
|
||||
})
|
||||
})
|
|
@ -1,73 +0,0 @@
|
|||
import assert from 'assert-extended'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { createContext } from '../helper.server'
|
||||
|
||||
describe('Media (Routes)', () => {
|
||||
const multer = require('../../api/media/multer')
|
||||
const routes = require('../../api/media/routes')
|
||||
const security = require('../../api/media/security')
|
||||
const config = require('../../config')
|
||||
|
||||
let sandbox
|
||||
let ctx
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.sandbox.create()
|
||||
ctx = createContext({
|
||||
req: {
|
||||
file: { },
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#upload', () => {
|
||||
let stubVerifyToken
|
||||
let stubUpload
|
||||
|
||||
beforeEach(() => {
|
||||
stubVerifyToken = sandbox.stub(security, 'verifyToken')
|
||||
stubUpload = sandbox.stub(multer, 'uploadFile').resolves({})
|
||||
})
|
||||
|
||||
it('should call security correctly', async () => {
|
||||
const assertError = new Error('temp')
|
||||
stubVerifyToken.rejects(assertError)
|
||||
|
||||
let err = await assert.isRejected(routes.upload(ctx))
|
||||
|
||||
assert.ok(stubVerifyToken.called)
|
||||
assert.strictEqual(err, assertError)
|
||||
assert.strictEqual(stubVerifyToken.firstCall.args[0], ctx)
|
||||
})
|
||||
|
||||
it('should call upload correctly', async () => {
|
||||
const assertSiteName = 'benshapiro'
|
||||
const assertError = new Error('hello')
|
||||
stubVerifyToken.resolves(assertSiteName)
|
||||
stubUpload.rejects(assertError)
|
||||
|
||||
let err = await assert.isRejected(routes.upload(ctx))
|
||||
|
||||
assert.ok(stubUpload.called)
|
||||
assert.strictEqual(err, assertError)
|
||||
assert.strictEqual(stubUpload.firstCall.args[0], ctx)
|
||||
assert.strictEqual(stubUpload.firstCall.args[1], assertSiteName)
|
||||
})
|
||||
|
||||
it('should otherwise set context status to 204 and file in result', async () => {
|
||||
const assertFilename = 'asdfsafd'
|
||||
const assertSite = 'mario'
|
||||
stubVerifyToken.resolves(assertSite)
|
||||
stubUpload.resolves({ filename: assertFilename })
|
||||
await routes.upload(ctx)
|
||||
|
||||
assert.strictEqual(ctx.body.filename, assertFilename)
|
||||
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}`)
|
||||
})
|
||||
})
|
||||
})
|
64
test/media/routes.test.mjs
Normal file
64
test/media/routes.test.mjs
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { Eltro as t, assert, stub } from 'eltro'
|
||||
import { createContext } from '../helper.server.mjs'
|
||||
|
||||
import MediaRoutes from '../../api/media/routes.mjs'
|
||||
|
||||
t.describe('#upload', () => {
|
||||
const stubVerify = stub()
|
||||
const stubUpload = stub()
|
||||
|
||||
const routes = new MediaRoutes({
|
||||
security: { verifyToken: stubVerify },
|
||||
multer: { uploadFile: stubUpload },
|
||||
})
|
||||
|
||||
function reset() {
|
||||
stubVerify.reset()
|
||||
stubUpload.reset()
|
||||
}
|
||||
|
||||
t.test('should call security correctly', async () => {
|
||||
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 () => {
|
||||
reset()
|
||||
|
||||
let ctx = createContext()
|
||||
const assertSiteName = 'benshapiro'
|
||||
const assertError = new Error('hello')
|
||||
stubVerify.resolves(assertSiteName)
|
||||
stubUpload.rejects(assertError)
|
||||
|
||||
let err = await assert.isRejected(routes.upload(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 () => {
|
||||
reset()
|
||||
|
||||
let ctx = createContext()
|
||||
const assertFilename = 'asdfsafd'
|
||||
const assertSite = 'mario'
|
||||
stubVerify.resolves(assertSite)
|
||||
stubUpload.resolves({ filename: assertFilename })
|
||||
await routes.upload(ctx)
|
||||
|
||||
assert.strictEqual(ctx.body.filename, assertFilename)
|
||||
assert.strictEqual(ctx.body.path, `/${assertSite}/${assertFilename}`)
|
||||
})
|
||||
})
|
|
@ -2,6 +2,7 @@ import { Eltro as t, assert} from 'eltro'
|
|||
|
||||
import { createContext } from '../helper.server.mjs'
|
||||
import { verifyToken } from '../../api/media/security.mjs'
|
||||
import { HttpError } from '../../api/error.mjs'
|
||||
import encode from '../../api/jwt/encode.mjs'
|
||||
import config from '../../api/config.mjs'
|
||||
|
||||
|
@ -16,51 +17,73 @@ t.describe('#verifyToken()', function() {
|
|||
|
||||
t.test('should fail if query token is missing', function() {
|
||||
let ctx = createContext({ })
|
||||
delete ctx.query.token
|
||||
ctx.query.delete('token')
|
||||
|
||||
assert.throws(function() { verifyToken(ctx) }, /[Mm]issing/)
|
||||
assert.throws(function() { verifyToken(ctx) }, function(err) {
|
||||
assert.ok(err instanceof HttpError)
|
||||
assert.ok(err instanceof Error)
|
||||
assert.strictEqual(err.status, 422)
|
||||
assert.match(err.message, /[Qq]uery/)
|
||||
assert.match(err.message, /[Tt]oken/)
|
||||
return true
|
||||
})
|
||||
})
|
||||
|
||||
function assertInvalidToken(err) {
|
||||
assert.ok(err instanceof HttpError)
|
||||
assert.ok(err instanceof Error)
|
||||
assert.strictEqual(err.status, 422)
|
||||
assert.match(err.message, /[Ii]nvalid/)
|
||||
assert.match(err.message, /[Tt]oken/)
|
||||
return true
|
||||
}
|
||||
|
||||
t.test('should fail if token is invalid', function() {
|
||||
let ctx = createContext({ })
|
||||
ctx.query.token = 'asdfasdgassdga'
|
||||
ctx.query.set('token', 'asdfasdgassdga')
|
||||
|
||||
assert.throws(function() { verifyToken(ctx) })
|
||||
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
||||
assert.ok(ctx.log.error.lastCall)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /3 dots/)
|
||||
|
||||
ctx.query.token = 'asdfasdgassdga.asdfasdg.sadfsadfas'
|
||||
ctx.query.set('token', 'asdfasdgassdga.asdfasdg.sadfsadfas')
|
||||
|
||||
assert.throws(function() { verifyToken(ctx) }, /invalid/)
|
||||
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /[Ii]nvalid/)
|
||||
|
||||
ctx.query.token = encode(
|
||||
ctx.query.set('token', encode(
|
||||
{ typ: 'JWT', alg: 'HS256' },
|
||||
{ iss: 'justatest' },
|
||||
'mysharedkey'
|
||||
)
|
||||
assert.throws(function() { verifyToken(ctx) }, /pubkey/)
|
||||
))
|
||||
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /pubkey/)
|
||||
|
||||
ctx.query.token = encode(
|
||||
ctx.query.set('token', encode(
|
||||
{ typ: 'JWT', alg: 'HS512' },
|
||||
{ iss: 'notexist' },
|
||||
'mysharedkey'
|
||||
)
|
||||
assert.throws(function() { verifyToken(ctx) }, /notexist/)
|
||||
))
|
||||
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /notexist/)
|
||||
|
||||
ctx.query.token = encode(
|
||||
ctx.query.set('token', encode(
|
||||
{ typ: 'JWT', alg: 'HS512' },
|
||||
{ iss: 'justatest' },
|
||||
'mysharedkey2'
|
||||
)
|
||||
assert.throws(function() { verifyToken(ctx) }, /HS512/)
|
||||
assert.throws(function() { verifyToken(ctx) }, /[vV]erification/)
|
||||
))
|
||||
assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /HS512/)
|
||||
assert.match(ctx.log.error.lastCall[0].message, /[vV]erification/)
|
||||
})
|
||||
|
||||
t.test('should otherwise return the issuer', function() {
|
||||
let ctx = createContext({ })
|
||||
ctx.query.token = encode(
|
||||
ctx.query.set('token', encode(
|
||||
{ typ: 'JWT', alg: 'HS512' },
|
||||
{ iss: 'justatest' },
|
||||
'mysharedkey'
|
||||
)
|
||||
))
|
||||
let site = verifyToken(ctx)
|
||||
assert.strictEqual(site, 'justatest')
|
||||
})
|
||||
|
|
|
@ -1,28 +1,40 @@
|
|||
import { Eltro as t, assert} from 'eltro'
|
||||
|
||||
import * as server from './helper.server.mjs'
|
||||
import { createClient, server, resetLog } from './helper.server.mjs'
|
||||
|
||||
t.describe('Server', function() {
|
||||
let client
|
||||
|
||||
t.before(function() {
|
||||
client = server.createClient()
|
||||
client = createClient()
|
||||
})
|
||||
|
||||
t.test('should run', async function() {
|
||||
resetLog()
|
||||
assert.strictEqual(server.log.info.callCount, 0)
|
||||
let data = await client.get('/')
|
||||
|
||||
assert.ok(data)
|
||||
assert.ok(data.name)
|
||||
assert.ok(data.version)
|
||||
assert.strictEqual(server.log.info.callCount, 1)
|
||||
assert.strictEqual(server.log.info.lastCall[0].status, 200)
|
||||
assert.strictEqual(server.log.info.lastCall[0].path, '/')
|
||||
assert.strictEqual(typeof(server.log.info.lastCall[0].ms), 'number')
|
||||
})
|
||||
|
||||
t.test('should handle errors fine', async function() {
|
||||
resetLog()
|
||||
assert.strictEqual(server.log.warn.callCount, 0)
|
||||
let data = await assert.isRejected(client.get('/error'))
|
||||
|
||||
assert.ok(data)
|
||||
assert.ok(data.body)
|
||||
assert.strictEqual(data.body.status, 422)
|
||||
assert.strictEqual(data.body.status, 500)
|
||||
assert.match(data.body.message, /test/)
|
||||
assert.strictEqual(server.log.warn.callCount, 1)
|
||||
assert.strictEqual(server.log.warn.lastCall[0].status, 500)
|
||||
assert.strictEqual(server.log.warn.lastCall[0].path, '/error')
|
||||
assert.strictEqual(typeof(server.log.warn.lastCall[0].ms), 'number')
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue