text: var should  = require('should');
  var request = require('request');
  var fs      = require('fs');
  var actionheroPrototype = require(__dirname + '/../../actionhero.js').actionheroPrototype;
  var actionhero = new actionheroPrototype();
  var api;
  var url
  
  describe('Server: Web', function(){
  
    before(function(done){
      actionhero.start(function(err, a){
        api = a;
        url = 'http://localhost:' + api.config.servers.web.port;
        done();
      })
    });
  
    after(function(done){
      actionhero.stop(function(){
        done();
      });
    });
  
    it('Server should be up and return data', function(done){
      request.get(url + '/api/', function(err, response, body){
        body = JSON.parse(body);
        body.should.be.an.instanceOf(Object);
        done();
      });
    });
  
    it('Server basic response should be JSON and have basic data', function(done){
      request.get(url + '/api/', function(err, response, body){
        body = JSON.parse(body);
        body.should.be.an.instanceOf(Object);
        body.requesterInformation.should.be.an.instanceOf(Object);
        done();
      });
    });
  
    it('params work', function(done){
      request.get(url + '/api?key=value', function(err, response, body){
        body = JSON.parse(body);
        body.requesterInformation.receivedParams.key.should.equal('value')
        done();
      });
    });
  
    it('params are ignored unless they are in the whitelist', function(done){
      request.get(url + '/api?crazyParam123=something', function(err, response, body){
        body = JSON.parse(body);
        should.not.exist(body.requesterInformation.receivedParams.crazyParam123);
        done();
      });
    });
  
    describe('if disableParamScrubbing is set ', function () {
      var orig;
  
      before(function(done) {
        orig = api.config.general.disableParamScrubbing;
        api.config.general.disableParamScrubbing = true;
        done();
      });
  
      after(function (done) {
        api.config.general.disableParamScrubbing = orig;
        done();
      })
  
        it('params are not ignored', function(done){
          request.get(url + '/api/testAction/?crazyParam123=something', function(err, response, body){
            body = JSON.parse(body);
            body.requesterInformation.receivedParams.crazyParam123.should.equal('something');
            done();
          });
        });
  
    });
  
    it('gibberish actions have the right response', function(done){
      request.get(url + '/api/IAMNOTANACTION', function(err, response, body){
        body = JSON.parse(body);
        body.error.should.equal('Error: unknown action or invalid apiVersion')
        done();
      });
    });
  
    it('real actions do not have an error response', function(done){
      request.get(url + '/api/status', function(err, response, body){
        body = JSON.parse(body);
        should.not.exist(body.error);
        done();
      });
    });
  
    it('HTTP Verbs should work: GET', function(done){
      request.get(url + '/api/randomNumber', function(err, response, body){
        body = JSON.parse(body);
        body.randomNumber.should.be.within(0,1)
        done();
      });
    });
  
    it('HTTP Verbs should work: PUT', function(done){
      request.put(url + '/api/randomNumber', function(err, response, body){
        body = JSON.parse(body);
        body.randomNumber.should.be.within(0,10)
        done();
      });
    });
  
    it('HTTP Verbs should work: POST', function(done){
      request.post(url + '/api/randomNumber', function(err, response, body){
        body = JSON.parse(body);
        body.randomNumber.should.be.within(0,100)
        done();
      });
    });
  
    it('HTTP Verbs should work: DELETE', function(done){
      request.del(url + '/api/randomNumber', function(err, response, body){
        body = JSON.parse(body);
        body.randomNumber.should.be.within(0,1000)
        done();
      });
    });
  
    it('HTTP Verbs should work: Post with Form', function(done){
      request.post(url + '/api/cacheTest', {form: {key:'key', value: 'value'}}, function(err, response, body){
        body = JSON.parse(body);
        body.cacheTestResults.saveResp.should.eql(true);
        done();
      });
    });
  
    it('HTTP Verbs should work: Post with JSON Payload as body', function(done){
      var body = JSON.stringify({key:'key', value: 'value'});
      request.post(url + '/api/cacheTest', {'body': body, 'headers': {'Content-type': 'application/json'}}, function(err, response, body){
        body = JSON.parse(body);
        body.cacheTestResults.saveResp.should.eql(true);
        done();
      });
    });
  
    describe('connection.rawConnection.params', function () {
      before(function(done){
        api.actions.versions.paramTestAction = [1]
        api.actions.actions.paramTestAction = {
          '1': {
            name: 'paramTestAction',
            description: 'I return connection.rawConnection.params',
            version: 1,
            inputs: { required: [], optional: [] },
            outputExample: {},
            run:function(api, connection, next){
              connection.response = connection.rawConnection.params;
              next(connection, true);
            }
          }
        }
  
        api.routes.loadRoutes();
        done();
      });
  
      after(function(done){
        delete api.actions.actions.paramTestAction;
        delete api.actions.versions.paramTestAction;
        done();
      });
  
  
      it('.query should contain unfiltered query params', function (done) {
        request.get(url + '/api/paramTestAction/?crazyParam123=something', function(err, response, body){
          body = JSON.parse(body);
          body.query.crazyParam123.should.equal('something');
          done();
        });
      })
  
      it('.body should contain unfiltered request body params', function (done) {
        var requestBody = JSON.stringify({key:'value'});
        request.post(url + '/api/paramTestAction', {'body': requestBody, 'headers': {'Content-type': 'application/json'}}, function(err, response, body){
          body = JSON.parse(body);
          body.body.key.should.eql('value');
          done();
        });
      })
    })
  
    it('returnErrorCodes false should still have a status of 200', function(done){
      api.config.servers.web.returnErrorCodes = false;
      request.del(url + '/api/', function(err, response, body){
        body = JSON.parse(body);
        response.statusCode.should.eql(200);
        done();
      });
    });
  
    it('returnErrorCodes can be opted to change http header codes', function(done){
      api.config.servers.web.returnErrorCodes = true;
      request.del(url + '/api/', function(err, response, body){
        body = JSON.parse(body);
        response.statusCode.should.eql(404);
        done();
      });
    });
  
    describe('http header', function(){
  
      before(function(done){
        api.config.servers.web.returnErrorCodes = true;
        api.actions.versions.headerTestAction = [1]
        api.actions.actions.headerTestAction = {
          '1': {
            name: 'headerTestAction',
            description: 'I am a test',
            version: 1,
            inputs: { required: [], optional: [] },
            outputExample: {},
            run:function(api, connection, next){
              connection.rawConnection.responseHeaders.push(['thing', 'A']);
              connection.rawConnection.responseHeaders.push(['thing', 'B']);
              connection.rawConnection.responseHeaders.push(['thing', 'C']);
              connection.rawConnection.responseHeaders.push(['Set-Cookie', 'value_1=1']);
              connection.rawConnection.responseHeaders.push(['Set-Cookie', 'value_2=2']);
              next(connection, true);
            }
          }
        }
  
        api.routes.loadRoutes();
        done();
      });
  
      after(function(done){
        delete api.actions.actions.headerTestAction;
        delete api.actions.versions.headerTestAction;
        done();
      })
  
      it('duplicate headers should be removed (in favor of the last set)', function(done){
        request.get(url + '/api/headerTestAction', function(err, response, body){
          body = JSON.parse(body);
          response.statusCode.should.eql(200);
          response.headers.thing.should.eql('C');
          done();
        });
      });
  
      it('but duplicate set-cookie requests should be allowed', function(done){
        request.get(url + '/api/headerTestAction', function(err, response, body){
          body = JSON.parse(body);
          response.statusCode.should.eql(200);
          response.headers['set-cookie'].length.should.eql(3); // 2 + session
          response.headers['set-cookie'][1].should.eql('value_1=1');
          response.headers['set-cookie'][0].should.eql('value_2=2');
          done();
        });
      });
  
      it('should respond to OPTIONS with only HTTP headers', function(done){
        request({method: 'options', url: url + '/api/cacheTest'}, function(err, response){
          response.statusCode.should.eql(200);
          response.headers['access-control-allow-methods'].should.equal('HEAD, GET, POST, PUT, PATCH, DELETE, OPTIONS, TRACE');
          response.headers['access-control-allow-origin'].should.equal('*');
          response.headers['content-length'].should.equal('0');
          done();
        });
      });
  
      it('should respond to TRACE with parsed params received', function(done){
        request({method: 'trace', url: url + '/api/x', form: {key: 'someKey', value: 'someValue'}}, function(err, response, body){
          body = JSON.parse(body);  
          response.statusCode.should.eql(200);
          body.receivedParams.key.should.equal('someKey');
          body.receivedParams.value.should.equal('someValue');
          done();
        });
      });
  
      it('should respond to HEAD requests just like GET, but with no body', function(done){
        request({method: 'head', url: url + '/api/headerTestAction'}, function(err, response, body){
          response.statusCode.should.eql(200);
          body.should.equal('');
          done();
        });
      });
  
      it('keeps sessions with browser_fingerprint', function(done){
        var j = request.jar()
        request.post({url: url+'/api', jar: j}, function(err, response1, body1){
          request.get({url: url+'/api', jar: j}, function(err, response2, body2){
            request.put({url: url+'/api', jar: j}, function(err, response3, body3){
              request.del({url: url+'/api', jar: j}, function(err, response4, body4){
                body1 = JSON.parse(body1);
                body2 = JSON.parse(body2);
                body3 = JSON.parse(body3);
                body4 = JSON.parse(body4);
  
                response1.headers['set-cookie'].should.exist;
                should.not.exist(response2.headers['set-cookie']);
                should.not.exist(response3.headers['set-cookie']);
                should.not.exist(response4.headers['set-cookie']);
  
                var fingerprint1 = body1.requesterInformation.id.split('-')[0];
                var fingerprint2 = body2.requesterInformation.id.split('-')[0];
                var fingerprint3 = body3.requesterInformation.id.split('-')[0];
                var fingerprint4 = body4.requesterInformation.id.split('-')[0];
  
                fingerprint1.should.equal(fingerprint2);
                fingerprint1.should.equal(fingerprint3);
                fingerprint1.should.equal(fingerprint4);
  
                fingerprint1.should.equal(body1.requesterInformation.fingerprint);
                fingerprint2.should.equal(body2.requesterInformation.fingerprint);
                fingerprint3.should.equal(body3.requesterInformation.fingerprint);
                fingerprint4.should.equal(body4.requesterInformation.fingerprint);
                done();
              });
            });
          });
        });
      });
  
    });
  
    describe('http returnErrorCodes true', function(){
  
      before(function(done){
        api.config.servers.web.returnErrorCodes = true;
        
        api.actions.versions.statusTestAction = [1]
        api.actions.actions.statusTestAction = {
          '1': {
            name: 'statusTestAction',
            description: 'I am a test',
            inputs: {
              key: {required:true}
            },
            run:function(api, connection, next){
              if(connection.params.key !== 'value'){
                connection.error = 'key != value';
                connection.rawConnection.responseHttpCode = 402;
              } else {
                connection.response.good = true;
              }
              next(connection, true);
            }
          }
        }
  
        api.actions.versions.brokenAction = [1]
        api.actions.actions.brokenAction = {
          '1': {
            name: 'brokenAction',
            description: 'I am broken',
            run:function(api, connection, next){
              BREAK; // undefiend
              next(connection, true);
            }
          }
        }
  
        api.routes.loadRoutes();
        done();
      });
  
      after(function(done){
        api.config.servers.web.returnErrorCodes = false;
        delete api.actions.versions.statusTestAction;
        delete api.actions.actions.statusTestAction;
        delete api.actions.versions.brokenAction;
        delete api.actions.actions.brokenAction;
        done();
      });
  
      it('actions that do not exists should return 404', function(done){
        request.post(url + '/api/aFakeAction', function(err, response, body){
          body = JSON.parse(body);
          response.statusCode.should.eql(404);
          done();
        });
      });
  
      it('missing params result in a 422', function(done){
        request.post(url + '/api/statusTestAction', function(err, response, body){
          body = JSON.parse(body);
          response.statusCode.should.eql(422);
          done();
        });
      });
  
      it('server errors should return a 500', function(done){
        if(api.config.general.actionDomains === true){
          request.post(url + '/api/brokenAction', function(err, response, body){
            body = JSON.parse(body);
            response.statusCode.should.eql(500);
            done();
          });
        }else{
          console.log("skipping broken action test; api.config.general.actionDomains != true")
          done();
        }
      });
  
      it('status codes can be set for errors', function(done){
        request.post(url + '/api/statusTestAction', {form: {key: 'bannana'}}, function(err, response, body){
          body = JSON.parse(body);
          body.error.should.eql('key != value');
          response.statusCode.should.eql(402);
          done();
        });
      });
  
      it('status code should still be 200 if everything is OK', function(done){
        request.post(url + '/api/statusTestAction', {form: {key: 'value'}}, function(err, response, body){
          body = JSON.parse(body);
          body.good.should.eql(true);
          response.statusCode.should.eql(200);
          done();
        });
      });
  
    });
  
    describe('documentation', function(){
  
      it('documentation can be returned via a documentation action', function(done){
        request.get(url + '/api/showDocumentation', function(err, response, body){
          body = JSON.parse(body);
          body.documentation.should.be.an.instanceOf(Object);
          done();
        });
      });
  
      it('should have actions with all the right parts', function(done){
        request.get(url + '/api/showDocumentation', function(err, response, body){
          body = JSON.parse(body);
          for(var actionName in body.documentation){
            for(var version in body.documentation[actionName]){
              var action = body.documentation[actionName][version];
              action.name.should.be.a.String;
              action.description.should.be.a.String;
              action.inputs.should.be.an.instanceOf(Object)
            }
          }
          done();
        });
      });
      
    });
  
    describe('files', function(){
  
      it('file: an HTML file', function(done){
        request.get(url + '/public/simple.html', function(err, response){
          response.statusCode.should.equal(200);
          response.body.should.equal('<h1>ActionHero</h1>\\nI am a flat file being served to you via the API from ./public/simple.html<br />');
          done();
        });
      });
  
      it('file: 404 pages', function(done){
        request.get(url + '/public/notARealFile', function(err, response){
          response.statusCode.should.equal(404)
          done();
        });
      });
  
      it('I should not see files outside of the public dir', function(done){
        request.get(url + '/public/../config.json', function(err, response){
          response.statusCode.should.equal(404);
          response.body.should.equal( api.config.errors.fileNotFound() );
          done();
        });
      });
  
      it('file: index page should be served when requesting a path (trailing slash)', function(done){
        request.get(url + '/public/', function(err, response){
          response.statusCode.should.equal(200);
          response.body.should.be.a.String;
          done();
        });
      });
  
      it('file: index page should be served when requesting a path (no trailing slash)', function(done){
        request.get(url + '/public', function(err, response){
          response.statusCode.should.equal(200);
          response.body.should.be.a.String;
          done();
        });
      });
  
      describe('can serve files from more than one directory', function(){
        var source = __dirname + '/../../public/simple.html'
  
        before(function(done){
          fs.createReadStream(source).pipe(fs.createWriteStream('/tmp/testFile.html'));
          api.config.general.paths.public.push('/tmp');
          process.nextTick(function(){ 
            done(); 
          });
        });
  
        after(function(done){
          fs.unlink('/tmp/testFile.html');
          api.config.general.paths.public.pop();
          process.nextTick(function(){ 
            done(); 
          });
        });
  
        it('works for secondary paths', function(done){
          request.get(url + '/public/testFile.html', function(err, response){
            response.statusCode.should.equal(200);
            response.body.should.equal('<h1>ActionHero</h1>\\nI am a flat file being served to you via the API from ./public/simple.html<br />');
            done();
          });
        });
      });
  
      describe('depth routes', function(){
        before(function(){
          api.config.servers.web.urlPathForActions = '/craz/y/action/path'
          api.config.servers.web.urlPathForFiles = '/a/b/c'
        })
  
        after(function(){
          api.config.servers.web.urlPathForActions = 'api'
          api.config.servers.web.urlPathForFiles = 'public'
        })
  
        it('old action routes stop working', function(done){
          request.get(url + '/api/randomNumber', function(err, response){
            response.statusCode.should.equal(404);
            done();
          });
        });
  
        it('can ask for nested URL actions', function(done){
          request.get(url + '/craz/y/action/path/randomNumber', function(err, response){
            response.statusCode.should.equal(200);
            done();
          });
        });
  
        it('old file routes stop working', function(done){
          request.get(url + '/public/simple.html', function(err, response){
            response.statusCode.should.equal(404);
            done();
          });
        });
  
        it('can ask for nested URL files', function(done){
          request.get(url + '/a/b/c/simple.html', function(err, response){
            response.statusCode.should.equal(200);
            response.body.should.equal('<h1>ActionHero</h1>\\nI am a flat file being served to you via the API from ./public/simple.html<br />');
            done();
          });
        });
  
        it('can ask for nested URL files with depth', function(done){
          request.get(url + '/a/b/c/css/actionhero.css', function(err, response){
            response.statusCode.should.equal(200);
            done();
          });
        });
      });
  
    });
  
    describe('routes', function(){
      
      before(function(done){
        api.actions.versions.mimeTestAction = [1]
        api.actions.actions.mimeTestAction = {
          '1': {
            name: 'mimeTestAction',
            description: 'I am a test',
            matchExtensionMimeType: true,
            inputs: {
              key: {required:true}
            },
            outputExample: {},
            run:function(api, connection, next){
              next(connection, true);
            }
          }
        }
  
        api.actions.versions.login = [1]
        api.actions.actions.login = {
          '1': {
            name: 'login',
            description: 'login',
            matchExtensionMimeType: true,
            inputs: {
              userID: {required:true}
            },
            outputExample: {},
            run:function(api, connection, next){
              connection.response.userID = connection.params.userID;
              next(connection, true);
            }
          }
        }
  
        api.params.buildPostVariables();
        api.routes.loadRoutes({
          all: [
            { path: '/user/:userID', action: 'user' }
          ],
          get: [
            { path: '/bogus/:bogusID', action: 'bogusAction' },
            { path: '/users', action: 'usersList' },
            { path: '/c/:key/:value', action: 'cacheTest' },
            { path: '/mimeTestAction/:key', action: 'mimeTestAction' },
            { path: '/thing', action: 'thing' },
            { path: '/thing/stuff', action: 'thingStuff' }
          ],
          post: [
            { path: '/login/:userID(^\\d{3}$)', action: 'login' }
          ]
        });
  
        done();
      });
  
      after(function(done){
        api.routes.routes = {};
        delete api.actions.versions.mimeTestAction;
        delete api.actions.actions.mimeTestAction;
        delete api.actions.versions.login;
        delete api.actions.actions.login;
        done();
      });
  
      it('new params will not be allowed in route definitions (an action should do it)', function(done){
        (api.params.postVariables.indexOf('bogusID') < 0).should.equal(true);
        done();
      });
  
      it('\'all\' routes are duplicated properly', function(done){
        ['get', 'post', 'put', 'delete'].forEach(function(verb){
          api.routes.routes[verb][0].action.should.equal('user');
          api.routes.routes[verb][0].path.should.equal('/user/:userID');
        });
        done();
      })
    
      it('unknown actions are still unknown', function(done){
        request.get(url + '/api/a_crazy_action', function(err, response, body){
          body = JSON.parse(body);
          body.error.should.equal('Error: unknown action or invalid apiVersion')
          done();
        });
      });
  
      it('explicit action declarations still override routed actions, if the defined action is real', function(done){
        request.get(url + '/api/user/123?action=randomNumber', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('randomNumber')
          done();
        });
      });
  
      it('route actions will override explicit actions, if the defined action is null', function(done){
        request.get(url + '/api/user/123?action=someFakeAction', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          done();
        });
      });
  
      it('Routes should be mapped for GET (simple)', function(done){
        request.get(url + '/api/users', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('usersList')
          done();
        });
      });
  
      it('Routes should be mapped for GET (complex)', function(done){
        request.get(url + '/api/user/1234', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          body.requesterInformation.receivedParams.userID.should.equal('1234')
          done();
        });
      });
  
      it('Routes should be mapped for POST', function(done){
        request.post(url + '/api/user/1234?key=value', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          body.requesterInformation.receivedParams.userID.should.equal('1234')
          body.requesterInformation.receivedParams.key.should.equal('value')
          done();
        });
      });
  
      it('Routes should be mapped for PUT', function(done){
        request.put(url + '/api/user/1234?key=value', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          body.requesterInformation.receivedParams.userID.should.equal('1234')
          body.requesterInformation.receivedParams.key.should.equal('value')
          done();
        });
      });
  
      it('Routes should be mapped for DELETE', function(done){
        request.del(url + '/api/user/1234?key=value', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          body.requesterInformation.receivedParams.userID.should.equal('1234')
          body.requesterInformation.receivedParams.key.should.equal('value')
          done();
        });
      });
  
      it('route params trump explicit params', function(done){
        request.get(url + '/api/user/1?userID=2', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('user')
          body.requesterInformation.receivedParams.userID.should.equal('1')
          done();
        });
      });
  
      it('to match, a route much match all parts of the URL', function(done){
        request.get(url + '/api/thing', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('thing')
          
          request.get(url + '/api/thing/stuff', function(err, response, body){
            body = JSON.parse(body);
            body.requesterInformation.receivedParams.action.should.equal('thingStuff')
            done();
          });
        });
      });
  
      it('regexp matches will provide proper variables', function(done){
        request.post(url + '/api/login/123', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('login');
          body.requesterInformation.receivedParams.userID.should.equal('123');
          done();
        });
      });
  
      it('regexp matches will still work with params with periods and other wacky chars', function(done){
        request.get(url + '/api/c/key/log_me-in.com$123.jpg', function(err, response, body){
          body = JSON.parse(body);
          body.requesterInformation.receivedParams.action.should.equal('cacheTest');
          body.requesterInformation.receivedParams.value.should.equal('log_me-in.com$123.jpg');
          done();
        });
      });
  
      it('regexp match failures will be rejected', function(done){
        request.post(url + '/api/login/1234', function(err, response, body){
          body = JSON.parse(body);
          body.error.should.equal('Error: unknown action or invalid apiVersion');
          should.not.exist(body.requesterInformation.receivedParams.userID);
          done();
        });
      });
  
      describe('file extensions + routes', function(){
  
        it('will change header information based on extension (when active)', function(done){
          request.get(url + '/api/mimeTestAction/val.png', function(err, response){
            response.headers['content-type'].should.equal('image/png');
            done();
          });
        });
  
        it('will not change header information if there is a connection.error', function(done){
          request.get(url + '/api/mimeTestAction', function(err, response, body){
            body = JSON.parse(body);
            response.headers['content-type'].should.equal('application/json; charset=utf-8');
            body.error.should.equal('Error: key is a required parameter for this action');
            done();
          });
        });
  
      });
  
    });
  
  });
  