const security = require('./index')
const express = require('express')
const superagent = require("superagent")

let app
let server

const mockReq = {
  originalUrl: '/',
  _setUrl: function (url) {
    this.originalUrl = url
  },
  method: 'GET',
  _setMethod: function(method) {
    this.method = method
  },
  app: {
    _router: {
      stack: [{
        route: {
          path: '/'
        }
      }]
    }
  }
}
const mockRes = {
  _headers: {
    'X-Powered-By': 'my-server'
  },
  set: function(header, value) {
    this._headers[header] = value
  },
  removeHeader: function(header) {
    delete this._headers[header]
  },
  _status: 200,
  status: function(status) {
    this._status = status
    return this
  },
  end: function() {
    return undefined
  }
}

describe('Unit Tests', () => {
  beforeEach(() => {
    mockRes._headers = { 'X-Powered-By': 'my-server'}
    mockRes._status = 200
    mockReq.originalUrl = '/'
    mockReq.method = 'GET'
  })
  headerUnitTest('Cache-Control', 'CacheControl', 'no-cache, no-store, must-revalidate')
  headerUnitTest('Pragma', 'Pragma', 'no-cache')
  headerUnitTest('Expires', 'Expires', '0')
  headerUnitTest('Content-Security-Policy', 'ContentSecurityPolicy', 'default-src \'self\'; frame-ancestors \'none\'')
  headerUnitTest('X-XSS-Protection', 'XXSSProtection', '1; mode=block')
  headerUnitTest('X-DNS-Prefetch-Control', 'XDNSPrefetchControl', 'off')
  headerUnitTest('Expect-CT', 'ExpectCT', 'enforce; max-age=30; report-uri="/_report"')
  headerUnitTest('X-Frame-Options', 'XFrameOptions', 'deny')
  describe('Header: X-Powered-By', () => {
    it('should remove Header if not defined', (done) => {
      let sec = security()
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._headers['X-Powered-By']).toBeUndefined()
        done()
      })
    })
    it('should not remove Header if set to false', (done) => {
      let options = {}
      options.XPoweredBy = false
      let sec = security(options)
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._headers['X-Powered-By']).toBeDefined()
        done()
      })
    })
  })
  headerUnitTest('Strict-Transport-Security', 'StrictTransportSecurity', 'max-age=30')
  headerUnitTest('X-Download-Options', 'XDownloadOptions', 'noopen')
  headerUnitTest('X-Content-Type-Options', 'XContentTypeOptions', 'nosniff')
  headerUnitTest('X-Permitted-Cross-Domain-Policies', 'XPermittedCrossDomainPolicies', 'none')
  headerUnitTest('Referrer-Policy', 'ReferrerPolicy', 'no-referrer')

  describe('Allowed Methods', () => {
    it('should only allow GET, POST, PUT, DELETE on default', (done) => {
      let sec = security()
      mockReq._setMethod('GET')
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._status).toBe(200)
        mockReq._setMethod('HEAD')
        sec.setHeaders(mockReq, mockRes, () => {
          expect(mockRes._status).toBe(405)
          done()
        })
      })
    })
    it('should allow given Methods', (done) => {
      let sec = security({
        allowedMethods: ['POST']
      })
      mockReq._setMethod('POST')
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._status).toBe(200)
        mockReq._setMethod('GET')
        sec.setHeaders(mockReq, mockRes, () => {
          expect(mockRes._status).toBe(405)
          done()
        })
      })
    })
  })
  describe('Defined Routes', () => {
    it('should allow all routes by default', (done) => {
      let sec = security()
      mockReq._setUrl('/')
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._status).toBe(200)
        mockReq._setUrl('/test')
        sec.setHeaders(mockReq, mockRes, () => {
          expect(mockRes._status).toBe(200)
          done()
        })
      })
    })
    it('should only allow defined routes if set to true', (done) => {
      let sec = security({
        onlyDefinedRoutes: true,
        definedRoutes: ['/']
      })
      mockReq._setUrl('/')
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._status).toBe(200)
        mockReq._setUrl('/test')
        sec.setHeaders(mockReq, mockRes, () => {
          expect(mockRes._status).toBe(405)
          done()
        })
      })
    })
  })
})

describe('Integration Tests', () => {
  afterEach(() => {
    server.close()
  })
  headerIntegrationTest('Cache-Control', 'CacheControl', 'no-cache, no-store, must-revalidate')
  headerIntegrationTest('Pragma', 'Pragma', 'no-cache')
  headerIntegrationTest('Expires', 'Expires', '0')
  headerIntegrationTest('Content-Security-Policy', 'ContentSecurityPolicy', 'default-src \'self\'; frame-ancestors \'none\'')
  headerIntegrationTest('X-XSS-Protection', 'XXSSProtection', '1; mode=block')
  headerIntegrationTest('X-DNS-Prefetch-Control', 'XDNSPrefetchControl', 'off')
  headerIntegrationTest('Expect-CT', 'ExpectCT', 'enforce; max-age=30; report-uri="/_report"')
  headerIntegrationTest('X-Frame-Options', 'XFrameOptions', 'deny')
  describe('Header: X-Powered-By', () => {
    it('should remove Header if not defined', (done) => {
      startUpServer({})
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          expect(res.headers['x-powered-by']).toBeUndefined()
          done()
        })
    })
    it('should not remove Header if set to false', (done) => {
      startUpServer({
        XPoweredBy: false
      })
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          expect(res.headers['x-powered-by']).toBeDefined()
          done()
        })
    })
  })
  headerIntegrationTest('Strict-Transport-Security', 'StrictTransportSecurity', 'max-age=30')
  headerIntegrationTest('X-Download-Options', 'XDownloadOptions', 'noopen')
  headerIntegrationTest('X-Content-Type-Options', 'XContentTypeOptions', 'nosniff')
  headerIntegrationTest('X-Permitted-Cross-Domain-Policies', 'XPermittedCrossDomainPolicies', 'none')
  headerIntegrationTest('Referrer-Policy', 'ReferrerPolicy', 'no-referrer')

  describe('Allowed Methods', () => {
    it('should only allow GET, POST, PUT, DELETE on default', (done) => {
      startUpServer({})
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          superagent
          .head('http://127.0.0.1:7777')
            .then(res2 => {})
            .catch((error) => {
              expect(error.status).toBe(405)
              done()
            })
        })
    })
    it('should allow given Methods', async () => {
      startUpServer({
        allowedMethods: ['POST']
      })
      const res = await superagent.post('http://127.0.0.1:7777').send({});
      expect(res.status).toBe(200)
      try {
        const res2 = await superagent.get('http://127.0.0.1:7777');
      } catch (error) {
        expect(error.status).toBe(405)
      }
    })
  })

  describe('Defined Routes', () => {
    it('should allow all routes by default', (done) => {
      startUpServer({})
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          superagent
          .get('http://127.0.0.1:7777/test')
            .then(res2 => {
              expect(res2.status).toBe(200)
              done()
            })
        })
    })
    it('should only allow defined routes if set to true', (done) => {
      startUpServer({
        onlyDefinedRoutes: true,
        definedRoutes: ['/']
      })
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          superagent
          .get('http://127.0.0.1:7777/test')
            .then(res2 => {})
            .catch((error) => {
              expect(error.status).toBe(405)
              done()
            })
        })
    })
    it('should not allow any routes if set to true but no routes given', (done) => {
      startUpServer({
        onlyDefinedRoutes: true
      })
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
        })
        .catch((error) => {
          expect(error.status).toBe(405)
          done()
        })
    })
    it('should allow regex route if set', (done) => {
      startUpServer({
        onlyDefinedRoutes: true,
        definedRoutes: ['/', 'REGEX:\\/test\\/\\d{1,}']
      })
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          superagent
          .get('http://127.0.0.1:7777/test')
            .then(res2 => {})
            .catch((error) => {
              expect(error.status).toBe(405)
              superagent
                .get('http://127.0.0.1:7777/test/123')
                  .then(res3 => {
                    expect(res3.status).toBe(200)
                    done()
                  })
            })
      })
    })
  })
})

function headerUnitTest (header, headerOption, defaultValue) {
  describe('Header: ' + header, () => {
    it('should set "' + defaultValue + '" if not defined', (done) => {
      let sec = security()
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._headers[header]).toBe(defaultValue)
        done()
      })
    })
    it('should not set Header if set to false', (done) => {
      let options = {}
      options[headerOption] = false
      let sec = security(options)
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._headers[header]).toBeUndefined()
        done()
      })
    })
    it('should set given values', (done) => {
      let options = {}
      options[headerOption] = 'somevalue'
      let sec = security(options)
      sec.setHeaders(mockReq, mockRes, () => {
        expect(mockRes._headers[header]).toBe('somevalue')
        done()
      })
    })
  })
}
function headerIntegrationTest (header, headerOption, defaultValue) {
  describe('Header: ' + header, () => {
    it('should set "' + defaultValue + '" if not defined', (done) => {
      startUpServer({})
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          expect(res.headers[header.toLowerCase()]).toBe(defaultValue)
          done()
        })
    })
    it('should not set Header if set to false', (done) => {
      let options = {}
      options[headerOption] = false
      startUpServer(options)
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          expect(res.headers[header.toLowerCase()]).toBeUndefined()
          done()
        })
    })
    it('should set given values', (done) => {
      let options = {}
      options[headerOption] = 'somevalue'
      startUpServer(options)
      superagent
        .get('http://127.0.0.1:7777')
        .then(res => {
          expect(res.status).toBe(200)
          expect(res.headers[header.toLowerCase()]).toBe('somevalue')
          done()
        })
    })
  })
}
function startUpServer(options) {
  app = express()
  let sec = security(options)
  app.use(sec.setHeaders)
  app.get('/', function (req, res) {
    res.send('Hello World!')
  })
  app.post('/', function (req, res) {
    res.send('Some post')
  })
  app.get('/test', function (req, res) {
    res.send('Hello Test!')
  })
  app.get('/test/123', function (req, res) {
    res.send('Hello 123!')
  })
  server = app.listen(7777)
}