'use strict';

const helper = require('../helper');
const fs = require('fs');
const mock = require('../../lib/index');

const assert = helper.assert;

describe('fs.rename(oldPath, newPath, callback)', function() {
  beforeEach(function() {
    mock({
      'path/to/a.bin': Buffer.from([1, 2, 3]),
      empty: {},
      nested: {
        dir: mock.directory({
          mtime: new Date(1),
          items: {'file.txt': ''}
        })
      }
    });
  });
  afterEach(mock.restore);

  it('allows files to be renamed', function(done) {
    fs.rename('path/to/a.bin', 'path/to/b.bin', function(err) {
      assert.isTrue(!err);
      assert.isFalse(fs.existsSync('path/to/a.bin'));
      assert.isTrue(fs.existsSync('path/to/b.bin'));
      done();
    });
  });

  it('supports Buffer input', function(done) {
    fs.rename(
      Buffer.from('path/to/a.bin'),
      Buffer.from('path/to/b.bin'),
      function(err) {
        assert.isTrue(!err);
        assert.isFalse(fs.existsSync('path/to/a.bin'));
        assert.isTrue(fs.existsSync('path/to/b.bin'));
        done();
      }
    );
  });

  it('promise allows files to be renamed', function(done) {
    fs.promises.rename('path/to/a.bin', 'path/to/b.bin').then(function() {
      assert.isFalse(fs.existsSync('path/to/a.bin'));
      assert.isTrue(fs.existsSync('path/to/b.bin'));
      done();
    }, done);
  });

  it('updates mtime of parent directory', function(done) {
    const oldTime = fs.statSync('nested/dir').mtime;
    fs.rename('nested/dir/file.txt', 'nested/dir/renamed.txt', function(err) {
      assert.isTrue(!err);
      assert.isFalse(fs.existsSync('nested/dir/file.txt'));
      assert.isTrue(fs.existsSync('nested/dir/renamed.txt'));
      const newTime = fs.statSync('nested/dir').mtime;
      assert.isTrue(newTime > oldTime);
      done();
    });
  });

  it('promise updates mtime of parent directory', function(done) {
    const oldTime = fs.statSync('nested/dir').mtime;
    fs.promises
      .rename('nested/dir/file.txt', 'nested/dir/renamed.txt')
      .then(function() {
        assert.isFalse(fs.existsSync('nested/dir/file.txt'));
        assert.isTrue(fs.existsSync('nested/dir/renamed.txt'));
        const newTime = fs.statSync('nested/dir').mtime;
        assert.isTrue(newTime > oldTime);
        done();
      }, done);
  });

  it('calls callback with error if old path does not exist', function(done) {
    fs.rename('bogus', 'empty', function(err) {
      assert.instanceOf(err, Error);
      done();
    });
  });

  it('promise calls callback with error if old path does not exist', function(done) {
    fs.promises.rename('bogus', 'empty').then(
      function() {
        done(new Error('Should not succeed.'));
      },
      function(err) {
        assert.instanceOf(err, Error);
        done();
      }
    );
  });

  it('overwrites existing files', function(done) {
    fs.rename('path/to/a.bin', 'nested/dir/file.txt', function(err) {
      assert.isTrue(!err);
      assert.isFalse(fs.existsSync('path/to/a.bin'));
      assert.isTrue(fs.existsSync('nested/dir/file.txt'));
      done();
    });
  });

  it('promise overwrites existing files', function(done) {
    fs.promises.rename('path/to/a.bin', 'nested/dir/file.txt').then(function() {
      assert.isFalse(fs.existsSync('path/to/a.bin'));
      assert.isTrue(fs.existsSync('nested/dir/file.txt'));
      done();
    }, done);
  });

  it('allows directories to be renamed', function(done) {
    fs.rename('path/to', 'path/foo', function(err) {
      assert.isTrue(!err);
      assert.isFalse(fs.existsSync('path/to'));
      assert.isTrue(fs.existsSync('path/foo'));
      assert.deepEqual(fs.readdirSync('path/foo'), ['a.bin']);
      done();
    });
  });

  it('promise allows directories to be renamed', function(done) {
    fs.promises.rename('path/to', 'path/foo').then(function() {
      assert.isFalse(fs.existsSync('path/to'));
      assert.isTrue(fs.existsSync('path/foo'));
      assert.deepEqual(fs.readdirSync('path/foo'), ['a.bin']);
      done();
    }, done);
  });

  it('calls callback with error if new directory not empty', function(done) {
    fs.rename('path', 'nested', function(err) {
      assert.instanceOf(err, Error);
      done();
    });
  });

  it('promise calls callback with error if new directory not empty', function(done) {
    fs.promises.rename('path', 'nested').then(
      function() {
        done(new Error('Should not succeed.'));
      },
      function(err) {
        assert.instanceOf(err, Error);
        done();
      }
    );
  });
});

describe('fs.renameSync(oldPath, newPath)', function() {
  beforeEach(function() {
    mock({
      'path/to/a.bin': Buffer.from([1, 2, 3]),
      empty: {},
      nested: {
        dir: {
          'file.txt': ''
        }
      },
      link: mock.symlink({path: './path/to/a.bin'})
    });
  });
  afterEach(mock.restore);

  it('allows files to be renamed', function() {
    fs.renameSync('path/to/a.bin', 'path/to/b.bin');
    assert.isFalse(fs.existsSync('path/to/a.bin'));
    assert.isTrue(fs.existsSync('path/to/b.bin'));
  });

  it('overwrites existing files', function() {
    fs.renameSync('path/to/a.bin', 'nested/dir/file.txt');
    assert.isFalse(fs.existsSync('path/to/a.bin'));
    assert.isTrue(fs.existsSync('nested/dir/file.txt'));
  });

  it('allows directories to be renamed', function() {
    fs.renameSync('path/to', 'path/foo');
    assert.isFalse(fs.existsSync('path/to'));
    assert.isTrue(fs.existsSync('path/foo'));
    assert.deepEqual(fs.readdirSync('path/foo'), ['a.bin']);
  });

  it('replaces existing directories (if empty)', function() {
    fs.renameSync('path/to', 'empty');
    assert.isFalse(fs.existsSync('path/to'));
    assert.isTrue(fs.existsSync('empty'));
    assert.deepEqual(fs.readdirSync('empty'), ['a.bin']);
  });

  it('renames symbolic links', function() {
    fs.renameSync('link', 'renamed');
    assert.isTrue(fs.existsSync('renamed'));
    assert.isFalse(fs.existsSync('link'));
    assert.isTrue(fs.existsSync('path/to/a.bin'));
  });

  it('throws if old path does not exist', function() {
    assert.throws(function() {
      fs.renameSync('bogus', 'empty');
    });
  });

  it('throws if new path basename is not directory', function() {
    assert.throws(function() {
      fs.renameSync('path/to/a.bin', 'bogus/a.bin');
    });
  });

  it('throws if new dir is not empty dir', function() {
    assert.throws(function() {
      fs.renameSync('path/to', 'nested');
    });
  });
});
