import fs from 'fs/promises'
import path from 'path'
import { Eltro as t, assert, stub } from 'eltro'
import { HttpError } from 'flaska'
import { createContext } from '../helper.server.mjs'

import MediaRoutes from '../../api/media/routes.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()

    await routes.init()

    assert.ok(routes.filesCacheGet('development'))
    assert.ok(Array.isArray(routes.filesCacheGet('development')))
    assert.ok(routes.filesCacheGet('existing'))
    assert.ok(Array.isArray(routes.filesCacheGet('existing')))
    assert.ok(routes.filesCacheGet('nonexisting'))
    assert.ok(Array.isArray(routes.filesCacheGet('nonexisting')))

    assert.strictEqual(routes.filesCacheGet('development').length, 1)
    assert.strictEqual(routes.filesCacheGet('existing').length, 2)
    assert.strictEqual(routes.filesCacheGet('nonexisting').length, 0)

    assert.strictEqual(routes.filesCacheGet('development')[0].filename, '.gitkeep')
    assert.strictEqual(routes.filesCacheGet('development')[0].size, null)
    assert.strictEqual(routes.filesCacheGet('existing')[0].filename, '20220105_101610_test1.jpg')
    assert.strictEqual(routes.filesCacheGet('existing')[0].size, null)
    assert.strictEqual(routes.filesCacheGet('existing')[1].filename, '20220105_101610_test2.png')
    assert.strictEqual(routes.filesCacheGet('existing')[1].size, null)
  })

  t.test('should always sort result', async function() {
    const routes = new MediaRoutes()

    await routes.init()

    let files = routes.filesCacheGet('existing')
    
    assert.strictEqual(files[0].filename, '20220105_101610_test1.jpg')
    assert.strictEqual(files[0].size, null)
    assert.strictEqual(files[1].filename, '20220105_101610_test2.png')
    assert.strictEqual(files[1].size, null)

    routes.siteCache.get('existing').push({ filename: '0000.png', size: 0 })

    files = routes.filesCacheGet('existing')
    assert.strictEqual(files[0].filename, '0000.png')
    assert.strictEqual(files[0].size, 0)
    assert.strictEqual(files[1].filename, '20220105_101610_test1.jpg')
    assert.strictEqual(files[1].size, null)
    assert.strictEqual(files[2].filename, '20220105_101610_test2.png')
    assert.strictEqual(files[2].size, null)
  })
})

t.describe('#listFiles()', function() {
  const stubVerify = stub()
  const stubGetCache = stub()
  
  const routes = new MediaRoutes({
    security: { verifyToken: stubVerify },
  })
  routes.filesCacheGet = stubGetCache

  function reset() {
    stubVerify.reset()
    stubGetCache.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.listFiles(ctx))

    assert.ok(stubVerify.called)
    assert.strictEqual(err, assertError)
    assert.strictEqual(stubVerify.firstCall[0], ctx)
  })

  t.test('should call filesCacheGet and return results', async function() {
    reset()

    let ctx = createContext()
    const assertSiteName = 'benshapiro'
    const assertResult = { a: 1 }
    stubVerify.resolves(assertSiteName)
    stubGetCache.returns(assertResult)

    await routes.listFiles(ctx)

    assert.ok(stubGetCache.called)
    assert.strictEqual(stubGetCache.firstCall[0], assertSiteName)
    assert.strictEqual(ctx.body, assertResult)
  })
})

t.describe('#listPublicFiles()', function() {
  const stubSitePublic = stub()
  const stubGetCache = stub()
  
  const routes = new MediaRoutes({
    security: { throwIfNotPublic: stubSitePublic },
  })
  routes.filesCacheGet = stubGetCache

  function reset() {
    stubSitePublic.reset()
    stubGetCache.reset()
  }

  t.test('should call security correctly', async function() {
    reset()

    let ctx = createContext()
    const assertError = new Error('temp')
    const assertSite = 'astalavista'
    stubSitePublic.throws(assertError)
    ctx.params.site = assertSite

    let err = await assert.isRejected(routes.listPublicFiles(ctx))

    assert.ok(stubSitePublic.called)
    assert.strictEqual(err, assertError)
    assert.strictEqual(stubSitePublic.firstCall[0], assertSite)
  })

  t.test('should call filesCacheGet and return results', async function() {
    reset()

    let ctx = createContext()
    const assertSiteName = 'benshapiro'
    const assertResult = { a: 1 }
    ctx.params.site = assertSiteName
    stubGetCache.returns(assertResult)

    await routes.listPublicFiles(ctx)

    assert.ok(stubGetCache.called)
    assert.strictEqual(stubGetCache.firstCall[0], assertSiteName)
    assert.strictEqual(ctx.body, assertResult)
  })
})

t.describe('#filesCacheAdd', function() {
  t.test('should auto-create array of site and add file', async function() {
    const routes = new MediaRoutes()
    const assertName = 'asdf.png'
    const assertSize = 1234

    await routes.init()

    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)
  })

  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('#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,
      verifyBody: stub(),
    },
    formidable: { uploadFile: stubUpload, },
    fs: { stat: stubStat },
  })

  function reset() {
    stubVerify.reset()
    stubUpload.reset()
    stubStat.reset()
  }
  
  t.test('should call upload correctly', async function() {
    reset()

    let ctx = createContext()
    const assertSiteName = 'benshapiro'
    const assertError = new Error('hello')
    stubVerify.resolves(assertSiteName)
    stubUpload.rejects(assertError)

    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)
    assert.strictEqual(stubUpload.firstCall[2], true)
  })
})

t.describe('#resizeExisting', function() {
  const stubVerifyToken = stub()
  const stubVerifyBody = stub()
  const stubSharp = stub()
  const stubSharpResize = stub()
  const stubSharpBlur = stub()
  const stubSharpTrim = stub()
  const stubSharpExtend = stub()
  const stubSharpFlatten = stub()
  const stubSharpRotate = stub()
  const stubSharpToFile = stub()
  const stubSharpJpeg = stub()
  const stubSharpPng = stub()
  const stubSharpToBuffer = stub()
  const stubStat = stub()

  const routes = new MediaRoutes({
    security: {
      verifyToken: stubVerifyToken,
      verifyBody: stubVerifyBody,
    },
    fs: { stat: stubStat },
    sharp: stubSharp,
  })

  function reset() {
    stubVerifyToken.reset()
    stubVerifyBody.reset()
    let def = {
      toFile: stubSharpToFile,
      jpeg: stubSharpJpeg,
      png: stubSharpPng,
      resize: stubSharpResize,
      trim: stubSharpTrim,
      extend: stubSharpExtend,
      flatten: stubSharpFlatten,
      blur: stubSharpBlur,
      rotate: stubSharpRotate,
      toBuffer: stubSharpToBuffer,
    }
    stubStat.reset()
    stubStat.resolves({ size: 0 })
    stubSharp.reset()
    stubSharp.returns(def)
    for (let key in def) {
      def[key].reset()
      def[key].returns(def)
    }
    stubSharpToFile.resolves()
    stubSharpToBuffer.resolves()
    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.resizeExisting(ctx))

    assert.ok(stubVerifyToken.called)
    assert.strictEqual(err, assertError)
    assert.strictEqual(stubVerifyToken.firstCall[0], ctx)
  })

  t.test('should call security verifyBody correctly', async function() {
    reset()

    let ctx = createContext({ req: { body: { }}})
    const assertError = new Error('temp')
    stubVerifyBody.throws(assertError)

    let err = await assert.isRejected(routes.resizeExisting(ctx))

    assert.strictEqual(err, assertError)
    assert.ok(stubVerifyBody.called)
    assert.strictEqual(stubVerifyBody.firstCall[0], ctx)
  })

  t.test('should call sharp correctly if items are specified', async function() {
    reset()
    const assertKey = 'asdf'
    const assertJpeg = { a: 1 }
    const assertFilename = 'asdfsafd'
    const assertSite = 'mario'
    stubVerifyToken.resolves(assertSite)

    let ctx = createContext({ req: { body: {
      [assertKey]: {
        format: 'jpeg',
      }
    }}})
    ctx.params.filename = assertFilename + '.png'
    ctx.req.body[assertKey].jpeg = assertJpeg

    await routes.resizeExisting(ctx)
    
    assert.ok(stubSharp.called)
    assert.match(stubSharp.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}\.png`))
    assert.ok(stubSharpRotate.called)
    assert.ok(stubSharpJpeg.called)
    assert.strictEqual(stubSharpJpeg.firstCall[0], assertJpeg)
    assert.ok(stubSharpToFile.called)
    assert.match(stubSharpToFile.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}_${assertKey}\.jpg`))
    assert.notOk(stubSharpPng.called)
    assert.notOk(stubSharpResize.called)
    assert.notOk(ctx.body.filename)
    assert.notOk(ctx.body.path)
    assert.strictEqual(ctx.body[assertKey].filename, `${assertFilename}_${assertKey}\.jpg`)
    assert.strictEqual(ctx.body[assertKey].path, `/${assertSite}/${assertFilename}_${assertKey}.jpg`)
  })

  let validOperations = [
    'resize',
    'blur',
    'trim',
    'extend',
    'flatten',
  ]

  let operationStubMap = {
    resize: stubSharpResize,
    blur: stubSharpBlur,
    trim: stubSharpTrim,
    extend: stubSharpExtend,
    flatten: stubSharpFlatten,
  }

  validOperations.forEach(function(operation) {
    t.test(`should call sharp correctly if items and ${operation} are specified`, async function() {
      reset()
      const assertKey = 'herpderp'
      const assertPng = { a: 1 }
      const assertPayload = { a: 1 }
      const assertFilename = 'asdfsafd'
      const assertSite = 'mario'
      stubVerifyToken.resolves(assertSite)
  
      let called = 0
      stubStat.returnWith(function() {
        called += 10
        return { size: called }
      })
  
      let ctx = createContext({ req: { body: {
        [assertKey]: {
          format: 'png',
        }
      }}})
      ctx.params.filename = assertFilename + '.png'
      ctx.req.body[assertKey].png = assertPng
      ctx.req.body[assertKey][operation] = assertPayload
  
      await routes.resizeExisting(ctx)
      
      assert.ok(stubSharp.called)
      assert.match(stubSharp.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}\.png`))
      assert.ok(stubSharpRotate.called)
      assert.ok(stubSharpPng.called)
      assert.strictEqual(stubSharpPng.firstCall[0], assertPng)
      assert.ok(stubSharpToFile.called)
      assert.match(stubSharpToFile.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}_${assertKey}\.png`))
      assert.notOk(stubSharpJpeg.called)

      assert.ok(operationStubMap[operation].called)
      assert.strictEqual(operationStubMap[operation].firstCall[0], assertPayload)

      assert.notOk(ctx.body.filename)
      assert.notOk(ctx.body.path)
      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, 1)
      assert.strictEqual(filesFromCache[0].filename, `${assertFilename}_${assertKey}\.png`)
      assert.strictEqual(filesFromCache[0].size, 10)
    })
  })
  
  t.test('should notify which item failed if one fails', async function() {
    reset()
    const assertValidKey1 = 'herp'
    const assertValidKey2 = 'derp'
    const assertErrorKey = 'throwmyerr'
    const assertErrorMessage = 'some message here'
    stubVerifyToken.resolves('asdf')

    let called = 0
    stubStat.returnWith(function() {
      called += 10
      return { size: called }
    })

    let ctx = createContext({ req: { body: {
      [assertValidKey1]: {
        format: 'png',
        png: { a: 1 },
      },
      [assertValidKey2]: {
        format: 'png',
        png: { a: 1 },
      },
      [assertErrorKey]: {
        format: 'png',
        png: { a: 1 },
      },
    }}})
    ctx.params.filename = 'file.png'

    stubSharpToFile.returnWith(function(file) {
      if (file.match(new RegExp(assertErrorKey))) {
        throw new Error(assertErrorMessage)
      }
    })

    let err = await assert.isRejected(routes.resizeExisting(ctx))
    
    assert.ok(err instanceof HttpError)
    assert.ok(err instanceof Error)
    assert.strictEqual(err.status, 422)
    assert.match(err.message, new RegExp(assertErrorKey))
    assert.match(err.message, new RegExp(assertErrorMessage))
  })
  
  t.test('should call sharp correctly and return base64 format if out is base64', async function() {
    reset()
    const assertKey = 'outtest'
    const assertFilename = 'asdfsafd.png'
    const assertSite = 'mario'
    const assertBase64Data = 'asdf1234'
    stubVerifyToken.resolves(assertSite)

    let ctx = createContext({ req: { body: {
      [assertKey]: {
        format: 'png',
        png: { a: 1 },
        out: 'base64',
      }
    }}})
    ctx.params.filename = assertFilename
    
    stubSharpToBuffer.resolves(Buffer.from(assertBase64Data))

    await routes.resizeExisting(ctx)
    
    assert.ok(stubSharp.called)
    assert.notOk(stubSharpToFile.called)
    assert.ok(stubSharpToBuffer.called)
    assert.ok(ctx.body[assertKey].base64)
    assert.ok(ctx.body[assertKey].base64.startsWith(`data:image/png;base64,`))
    let base64 = ctx.body[assertKey].base64
    let bufferBase64 = Buffer.from(base64.slice(base64.indexOf(',')), 'base64')
    assert.strictEqual(bufferBase64.toString(), assertBase64Data)
    
    let filesFromCache = routes.filesCacheGet(assertSite)
    assert.strictEqual(filesFromCache.length, 0)
  })
})

t.describe('#resize', function() {
  const stubVerifyToken = stub()
  const stubVerifyBody = stub()
  const stubUpload = stub()
  const stubSharp = stub()
  const stubSharpResize = stub()
  const stubSharpBlur = stub()
  const stubSharpTrim = stub()
  const stubSharpExtend = stub()
  const stubSharpFlatten = stub()
  const stubSharpRotate = stub()
  const stubSharpToFile = stub()
  const stubSharpJpeg = stub()
  const stubSharpPng = stub()
  const stubSharpToBuffer = stub()
  const stubStat = stub()

  const routes = new MediaRoutes({
    security: {
      verifyToken: stubVerifyToken,
      verifyBody: stubVerifyBody,
    },
    fs: { stat: stubStat },
    formidable: { uploadFile: stubUpload },
    sharp: stubSharp,
  })

  function reset() {
    stubVerifyToken.reset()
    stubVerifyBody.reset()
    stubUpload.reset()
    let def = {
      toFile: stubSharpToFile,
      jpeg: stubSharpJpeg,
      png: stubSharpPng,
      resize: stubSharpResize,
      trim: stubSharpTrim,
      extend: stubSharpExtend,
      flatten: stubSharpFlatten,
      blur: stubSharpBlur,
      rotate: stubSharpRotate,
      toBuffer: stubSharpToBuffer,
    }
    stubStat.reset()
    stubStat.resolves({ size: 0 })
    stubSharp.reset()
    stubSharp.returns(def)
    for (let key in def) {
      def[key].reset()
      def[key].returns(def)
    }
    stubSharpToFile.resolves()
    stubSharpToBuffer.resolves()
    routes.siteCache.clear()
  }

  t.test('should call security verifyBody correctly', async function() {
    reset()

    let ctx = createContext({ req: { body: { }}})
    const assertError = new Error('temp')
    stubUpload.resolves({ filename: 'bla' })
    stubVerifyBody.throws(assertError)

    let err = await assert.isRejected(routes.resize(ctx))

    assert.strictEqual(err, assertError)
    assert.ok(stubVerifyBody.called)
    assert.strictEqual(stubVerifyBody.firstCall[0], ctx)
  })

  t.test('should call sharp correctly if items are specified', async function() {
    reset()
    const assertKey = 'asdf'
    const assertJpeg = { a: 1 }
    const assertFilename = 'asdfsafd'
    const assertSite = 'mario'
    stubVerifyToken.resolves(assertSite)
    stubUpload.resolves({ filename: assertFilename + '.png' })

    let ctx = createContext({ req: { body: {
      [assertKey]: {
        format: 'jpeg',
      }
    }}})
    ctx.req.body[assertKey].jpeg = assertJpeg

    await routes.resize(ctx)
    
    assert.ok(stubSharp.called)
    assert.match(stubSharp.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}\.png`))
    assert.ok(stubSharpRotate.called)
    assert.ok(stubSharpJpeg.called)
    assert.strictEqual(stubSharpJpeg.firstCall[0], assertJpeg)
    assert.ok(stubSharpToFile.called)
    assert.match(stubSharpToFile.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}_${assertKey}\.jpg`))
    assert.notOk(stubSharpPng.called)
    assert.notOk(stubSharpResize.called)
    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`)
  })

  let validOperations = [
    'resize',
    'blur',
    'trim',
    'extend',
    'flatten',
  ]

  let operationStubMap = {
    resize: stubSharpResize,
    blur: stubSharpBlur,
    trim: stubSharpTrim,
    extend: stubSharpExtend,
    flatten: stubSharpFlatten,
  }

  validOperations.forEach(function(operation) {
    t.test(`should call sharp correctly if items and ${operation} are specified`, async function() {
      reset()
      const assertKey = 'herpderp'
      const assertPng = { a: 1 }
      const assertPayload = { a: 1 }
      const assertFilename = 'asdfsafd'
      const assertSite = 'mario'
      stubVerifyToken.resolves(assertSite)
      stubUpload.resolves({ filename: assertFilename + '.png' })
  
      let called = 0
      stubStat.returnWith(function() {
        called += 10
        return { size: called }
      })
  
      let ctx = createContext({ req: { body: {
        [assertKey]: {
          format: 'png',
        }
      }}})
      ctx.req.body[assertKey].png = assertPng
      ctx.req.body[assertKey][operation] = assertPayload
  
      await routes.resize(ctx)
      
      assert.ok(stubSharp.called)
      assert.match(stubSharp.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}\.png`))
      assert.ok(stubSharpRotate.called)
      assert.ok(stubSharpPng.called)
      assert.strictEqual(stubSharpPng.firstCall[0], assertPng)
      assert.ok(stubSharpToFile.called)
      assert.match(stubSharpToFile.firstCall[0], new RegExp(`\/${assertSite}\/${assertFilename}_${assertKey}\.png`))
      assert.notOk(stubSharpJpeg.called)

      assert.ok(operationStubMap[operation].called)
      assert.strictEqual(operationStubMap[operation].firstCall[0], assertPayload)

      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].filename, `${assertFilename}_${assertKey}\.png`)
      assert.strictEqual(filesFromCache[0].size, 20)
      assert.strictEqual(filesFromCache[1].filename, assertFilename + '.png')
      assert.strictEqual(filesFromCache[1].size, 10)
    })
  })
  
  t.test('should notify which item failed if one fails', async function() {
    reset()
    const assertValidKey1 = 'herp'
    const assertValidKey2 = 'derp'
    const assertErrorKey = 'throwmyerr'
    const assertErrorMessage = 'some message here'
    stubVerifyToken.resolves('asdf')
    stubUpload.resolves({ filename: 'file.png' })

    let called = 0
    stubStat.returnWith(function() {
      called += 10
      return { size: called }
    })

    let ctx = createContext({ req: { body: {
      [assertValidKey1]: {
        format: 'png',
        png: { a: 1 },
      },
      [assertValidKey2]: {
        format: 'png',
        png: { a: 1 },
      },
      [assertErrorKey]: {
        format: 'png',
        png: { a: 1 },
      },
    }}})

    stubSharpToFile.returnWith(function(file) {
      if (file.match(new RegExp(assertErrorKey))) {
        throw new Error(assertErrorMessage)
      }
    })

    let err = await assert.isRejected(routes.resize(ctx))
    
    assert.ok(err instanceof HttpError)
    assert.ok(err instanceof Error)
    assert.strictEqual(err.status, 422)
    assert.match(err.message, new RegExp(assertErrorKey))
    assert.match(err.message, new RegExp(assertErrorMessage))
  })
  
  t.test('should call sharp correctly and return base64 format if out is base64', async function() {
    reset()
    const assertKey = 'outtest'
    const assertFilename = 'asdfsafd.png'
    const assertSite = 'mario'
    const assertBase64Data = 'asdf1234'
    stubVerifyToken.resolves(assertSite)
    stubUpload.resolves({ filename: assertFilename })

    let ctx = createContext({ req: { body: {
      [assertKey]: {
        format: 'png',
        png: { a: 1 },
        out: 'base64',
      }
    }}})
    
    stubSharpToBuffer.resolves(Buffer.from(assertBase64Data))

    await routes.resize(ctx)
    
    assert.ok(stubSharp.called)
    assert.notOk(stubSharpToFile.called)
    assert.ok(stubSharpToBuffer.called)
    assert.ok(ctx.body[assertKey].base64)
    assert.ok(ctx.body[assertKey].base64.startsWith(`data:image/png;base64,`))
    let base64 = ctx.body[assertKey].base64
    let bufferBase64 = Buffer.from(base64.slice(base64.indexOf(',')), 'base64')
    assert.strictEqual(bufferBase64.toString(), assertBase64Data)
    
    let filesFromCache = routes.filesCacheGet(assertSite)
    assert.strictEqual(filesFromCache.length, 1)
    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], path.join(`./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)
  })
})
