/*
 * provider-test.js: Tests for the nconf Provider object.
 *
 * (C) 2011, Charlie Robbins and the Contributors.
 *
 */

var assert = require('assert'),
    fs = require('fs'),
    path = require('path'),
    vows = require('vows'),
    helpers = require('./helpers'),
    nconf = require('../lib/nconf');

var fixturesDir = path.join(__dirname, 'fixtures'),
    mergeFixtures = path.join(fixturesDir, 'merge'),
    files = [path.join(mergeFixtures, 'file1.json'), path.join(mergeFixtures, 'file2.json')],
    override = JSON.parse(fs.readFileSync(files[0]), 'utf8');

function assertProvider(test) {
  return {
    topic: new nconf.Provider(),
    "should use the correct File store": test
  };
}

vows.describe('nconf/provider').addBatch({
  "When using nconf": {
    "an instance of 'nconf.Provider'": {
      "calling the use() method with the same store type and different options": {
        topic: new nconf.Provider().use('file', { file: files[0] }),
        "should use a new instance of the store type": function (provider) {
          var old = provider.stores['file'];

          assert.equal(provider.stores.file.file, files[0]);
          provider.use('file', { file: files[1] });

          assert.notStrictEqual(old, provider.stores.file);
          assert.equal(provider.stores.file.file, files[1]);
        }
      },
      "when 'argv' is true": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'provider-argv.js'),
        argv: ['--something', 'foobar']
      }),
      "when 'env' is true": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'provider-env.js'),
        env: { SOMETHING: 'foobar' }
      }),
      "when 'env' is true and 'parseValues' option is true": {
        topic: function() {
          var env = {
            SOMETHING: 'foobar',
            SOMEBOOL: 'true',
            SOMENULL: 'null',
            SOMEUNDEF: 'undefined',
            SOMEINT: '3600',
            SOMEFLOAT: '0.5',
            SOMEBAD: '5.1a'
          };
          var oenv = {};
          Object.keys(env).forEach(function (key) {
              if (process.env[key]) oenv[key] = process.env[key];
              process.env[key] = env[key];
          });
          var provider = new nconf.Provider().use('env', {parseValues: true});
          Object.keys(env).forEach(function (key) {
              delete process.env[key];
              if (oenv[key]) process.env[key] = oenv[key];
          });
          return provider;
        },
        "should respond with parsed values": function (provider) {

          assert.equal(provider.get('SOMETHING'), 'foobar');
          assert.strictEqual(provider.get('SOMEBOOL'), true);
          assert.notEqual(provider.get('SOMEBOOL'), 'true');
          assert.strictEqual(provider.get('SOMENULL'), null);
          assert.strictEqual(provider.get('SOMEUNDEF'), undefined);
          assert.strictEqual(provider.get('SOMEINT'), 3600);
          assert.strictEqual(provider.get('SOMEFLOAT'), .5);
          assert.strictEqual(provider.get('SOMEBAD'), '5.1a');
        }
      }
    },
    "the default nconf provider": {
      "when 'argv' is set to true": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'nconf-argv.js'),
        argv: ['--something', 'foobar'],
        env: { SOMETHING: true }
      }),
      "when 'env' is set to true": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'nconf-env.js'),
        env: { SOMETHING: 'foobar' }
      }),
      "when 'argv' is set to true and process.argv is modified": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'nconf-change-argv.js'),
        argv: ['--something', 'badValue', 'evenWorse', 'OHNOEZ', 'foobar']
      }),
      "when hierarchical 'argv' get": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'nconf-hierarchical-file-argv.js'),
        argv: ['--something', 'foobar'],
        env: { SOMETHING: true }
      }),
      "when 'env' is set to true with a nested separator": helpers.assertSystemConf({
        script: path.join(fixturesDir, 'scripts', 'nconf-nested-env.js'),
        env: { SOME_THING: 'foobar' }
      })
    }
  }
}).addBatch({
  "When using nconf": {
    "an instance of 'nconf.Provider'": {
      "the merge() method": {
        topic: new nconf.Provider().use('file', { file: files[1] }),
        "should have the result merged in": function (provider) {
          provider.load();
          provider.merge(override);
          helpers.assertMerged(null, provider.stores.file.store);
          assert.equal(provider.stores.file.store.candy.something, 'file1');
        },
        "should merge Objects over null": function (provider) {
          provider.load();
          provider.merge(override);
          assert.equal(provider.stores.file.store.unicorn.exists, true);
        }
      }
    }
  }
}).addBatch({
  "When using nconf": {
    "an instance of 'nconf.Provider'": {
      "the load() method": {
        "when sources are passed in": {
          topic: new nconf.Provider({
            sources: {
              user: {
                type: 'file',
                file: files[0]
              },
              global: {
                type: 'file',
                file: files[1]
              }
            }
          }),
          "should respect the hierarchy ": function (provider) {
            var merged = provider.load();

            helpers.assertMerged(null, merged);
            assert.equal(merged.candy.something, 'file1');
          }
        },
        "when multiple stores are used": {
          topic: new nconf.Provider().overrides({foo: {bar: 'baz'}})
            .add('file1', {type: 'file', file: files[0]})
            .add('file2', {type: 'file', file: files[1]}),
          "should respect the hierarchy": function(provider) {
            var merged = provider.load();

            helpers.assertMerged(null, merged);
            assert.equal(merged.foo.bar, 'baz');
            assert.equal(merged.candy.something, 'file1');
          }
        }
      }
    }
  }
}).addBatch({
  "When using nconf": {
    "an instance of 'nconf.Provider'": {
      "the .file() method": {
        "with a single filepath": assertProvider(function (provider) {
          provider.file(helpers.fixture('store.json'));
          assert.isObject(provider.stores.file);
        }),
        "with a name and a filepath": assertProvider(function (provider) {
          provider.file('custom', helpers.fixture('store.json'));
          assert.isObject(provider.stores.custom);
        }),
        "with a single object": assertProvider(function (provider) {
          provider.file({
            dir: helpers.fixture(''),
            file: 'store.json',
            search: true
          });

          assert.isObject(provider.stores.file);
          assert.equal(provider.stores.file.file, helpers.fixture('store.json'));
        }),
        "with a name and an object": assertProvider(function (provider) {
          provider.file('custom', {
            dir: helpers.fixture(''),
            file: 'store.json',
            search: true
          });

          assert.isObject(provider.stores.custom);
          assert.equal(provider.stores.custom.file, helpers.fixture('store.json'));
        })
      }
    }
  }
}).addBatch({
  "When using nconf": {
    "an instance of 'nconf.Provider'": {
      "the any() method": {
        topic: new nconf.Provider({
          type: 'literal',
          store: {
              key: "getThisValue"
          }
        }),
        "without a callback": {
          "given an array of keys with one matching": {
            "should respond with the correct value": function (provider) {
              assert.equal(provider.any(["notthis", "orthis", "key"]), 'getThisValue');
            }
          },
          "given an array of keys with no match": {
            "should respond with null": function (provider) {
              assert.isNull(provider.any(["notthis", "orthis"]));
            }
          },
          "given a variable argument list of keys with one matching": {
            "should respond with the correct value": function (provider) {
              assert.equal(provider.any("notthis", "orthis", "key"), 'getThisValue');
            }
          },
          "given no arguments": {
            "should respond with null": function (provider) {
              assert.isNull(provider.any());
            }
          }
        },
        "with a callback": {
          "given an array of keys with one matching": {
            topic: function(provider) {
              provider.any(["notthis", "orthis", "key"], this.callback);
            },
            "should respond with the correct value": function (err, value) {
              assert.equal(value, 'getThisValue');
            }
          },
          "given an array of keys with no match": {
            topic: function(provider) {
              provider.any(["notthis", "orthis"], this.callback);
            },
            "should respond with an undefined value": function (err, value) {
              assert.isUndefined(value);
            }
          },
          "given a variable argument list of keys with one matching": {
            topic: function(provider) {
              provider.any("notthis", "orthis", "key", this.callback);
            },
            "should respond with the correct value": function (err, value) {
              assert.equal(value, 'getThisValue');
            }
          },
          "given no keys": {
            topic: function(provider) {
              provider.any(this.callback);
            },
            "should respond with an undefined value": function (err, value) {
              assert.isUndefined(value);
            }
          }
        }
      }
    }
  }
}).export(module);
