<commit_before>'use strict';

/* jshint -W030 */
var chai = require('chai')
  , expect = chai.expect
  , Support = require(__dirname + '/support')
  , dialect = Support.getTestDialect()
  , Promise = require(__dirname + '/../../lib/promise')
  , Transaction = require(__dirname + '/../../lib/transaction')
  , sinon = require('sinon')
  , current = Support.sequelize;

if (current.dialect.supports.transactions) {

describe(Support.getTestDialectTeaser('Transaction'), function() {
  beforeEach(function () {
    this.sinon = sinon.sandbox.create();
  });

  afterEach(function () {
    this.sinon.restore();
  });

  this.timeout(5000);
  describe('constructor', function() {
    it('stores options', function() {
      var transaction = new Transaction(this.sequelize);
      expect(transaction.options).to.be.an.instanceOf(Object);
    });

    it('generates an identifier', function() {
      var transaction = new Transaction(this.sequelize);
      expect(transaction.id).to.exist;
    });
  });

  describe('commit', function() {
    it('is a commit method available', function() {
      expect(Transaction).to.respondTo('commit');
    });
  });

  describe('rollback', function() {
    it('is a rollback method available', function() {
      expect(Transaction).to.respondTo('rollback');
    });
  });

  describe('autoCallback', function() {
    it('supports automatically committing', function() {
      return this.sequelize.transaction(function() {
        return Promise.resolve();
      });
    });

    it('supports automatically rolling back with a thrown error', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
        throw new Error('Yolo');
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    it('supports automatically rolling back with a rejection', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
        return Promise.reject(new Error('Swag'));
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    it('errors when no promise chain is returned', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    if (dialect === 'postgres' || dialect === 'mssql') {
      it('do not rollback if already committed', function() {
        var SumSumSum = this.sequelize.define('transaction', {
              value: {
                type: Support.Sequelize.DECIMAL(10, 3),
                field: 'value'
              }
            })
          , self = this
          , transTest = function (val) {
              return self.sequelize.transaction({isolationLevel: 'SERIALIZABLE'}, function(t) {
                return SumSumSum.sum('value', {transaction: t}).then(function (balance) {
                  return SumSumSum.create({value: -val}, {transaction: t});
                });
              });
            };
        // Attention: this test is a bit racy. If you find a nicer way to test this: go ahead
        return SumSumSum.sync({force: true}).then(function () {
          return (expect(Promise.join(transTest(80), transTest(80), transTest(80))).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'));
        }).delay(100).then(function() {
          if (self.sequelize.test.$runningQueries !== 0) {
            return self.sequelize.Promise.delay(200);
          }
          return void 0;
        }).then(function() {
          if (self.sequelize.test.$runningQueries !== 0) {
            return self.sequelize.Promise.delay(500);
          }
        });
      });
    }

  });

  it('does not allow queries after commit', function() {
    var self = this;
    return expect(
      this.sequelize.transaction().then(function(t) {
        return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true}).then(function() {
          return t.commit();
        }).then(function() {
          return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true});
        });
      })
    ).to.eventually.be.rejected;
  });

  it('does not allow queries after rollback', function() {
    var self = this;
    return expect(
      this.sequelize.transaction().then(function(t) {
        return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true}).then(function() {
          return t.rollback();
        }).then(function() {
          return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true});
        });
      })
    ).to.eventually.be.rejected;
  });

  it('does not allow commits after commit', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.commit().then(function () {
        return t.commit();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be committed because it has been finished with state: commit');
  });

  it('does not allow commits after rollback', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.rollback().then(function () {
        return t.commit();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be committed because it has been finished with state: rollback');
  });

  it('does not allow rollbacks after commit', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.commit().then(function () {
        return t.rollback();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be rolled back because it has been finished with state: commit');
  });

  it('does not allow rollbacks after rollback', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.rollback().then(function () {
        return t.rollback();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be rolled back because it has been finished with state: rollback');
  });

  it('works even if a transaction: null option is passed', function () {
    this.sinon.spy(this.sequelize, 'query');

    return this.sequelize.transaction({
      transaction: null
    }).bind(this).then(function (t) {
      return t.commit().bind(this).then(function () {
        expect(this.sequelize.query.callCount).to.be.greaterThan(0);

        for (var i = 0; i < this.sequelize.query.callCount; i++) {
          expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t);
        }
      });
    });
  });

  it('works even if a transaction: undefined option is passed', function () {
    this.sinon.spy(this.sequelize, 'query');

    return this.sequelize.transaction({
      transaction: undefined
    }).bind(this).then(function (t) {
      return t.commit().bind(this).then(function () {
        expect(this.sequelize.query.callCount).to.be.greaterThan(0);

        for (var i = 0; i < this.sequelize.query.callCount; i++) {
          expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t);
        }
      });
    });
  });

  if (dialect === 'sqlite'){
    it('provides persistent transactions', function () {
      var sequelize = new Support.Sequelize('database', 'username', 'password', {dialect: 'sqlite'})
        , User = sequelize.define('user', {
            username: Support.Sequelize.STRING,
            awesome: Support.Sequelize.BOOLEAN
          })
        , persistentTransaction;

      return sequelize.transaction().then(function(t) {
        return sequelize.sync({ transaction:t }).then(function( ) {
          return t;
        });
      }).then(function(t) {
        return User.create({}, {transaction:t}).then(function( ) {
          return t.commit();
        });
      }).then(function() {
        return sequelize.transaction().then(function(t) {
          persistentTransaction = t;
        });
      }).then(function() {
        return User.findAll({transaction: persistentTransaction}).then(function(users) {
          expect(users.length).to.equal(1);
          return persistentTransaction.commit();
        });
      });
    });
  }

  if (current.dialect.supports.transactionOptions.type) {
    describe('transaction types', function() {
      it('should support default transaction type DEFERRED', function() {
        return this.sequelize.transaction({
        }).bind(this).then(function (t) {
          return t.rollback().bind(this).then(function() {
            expect(t.options.type).to.equal('DEFERRED');
          });
        });
      });

      Object.keys(Transaction.TYPES).forEach(function(key) {
        it('should allow specification of ' + key + ' type', function() {
          return this.sequelize.transaction({
            type: key
          }).bind(this).then(function (t) {
            return t.rollback().bind(this).then(function() {
              expect(t.options.type).to.equal(Transaction.TYPES[key]);
            });
          });
        });
      });

    });

    it('automatically retries on SQLITE_BUSY failure', function () {
      return Support.prepareTransactionTest(this.sequelize).bind({}).then(function(sequelize) {
        var User = sequelize.define('User', { username: Support.Sequelize.STRING });
        return User.sync({ force: true }).then(function() {
          var newTransactionFunc = function() {
            return sequelize.transaction({type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE}).then(function(t){
              return User.create({}, {transaction:t}).then(function( ) {
                return t.commit();
              });
            });
          };
          return Promise.join(newTransactionFunc(), newTransactionFunc()).then(function(results) {
            return User.findAll().then(function(users) {
              expect(users.length).to.equal(2);
            });
          });
        });
      });
    });

    it('fails with SQLITE_BUSY when retry.match is changed', function () {
      return Support.prepareTransactionTest(this.sequelize).bind({}).then(function(sequelize) {
        var User = sequelize.define('User', { id: {type: Support.Sequelize.INTEGER, primaryKey: true}, username: Support.Sequelize.STRING });
        return User.sync({ force: true }).then(function() {
          var newTransactionFunc = function() {
            return sequelize.transaction({type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: {match: ['NO_MATCH']}}).then(function(t){
              // introduce delay to force the busy state race condition to fail<commit_msg>Corrected retry query unit test to only run on sqlite.<commit_after>'use strict';

/* jshint -W030 */
var chai = require('chai')
  , expect = chai.expect
  , Support = require(__dirname + '/support')
  , dialect = Support.getTestDialect()
  , Promise = require(__dirname + '/../../lib/promise')
  , Transaction = require(__dirname + '/../../lib/transaction')
  , sinon = require('sinon')
  , current = Support.sequelize;

if (current.dialect.supports.transactions) {

describe(Support.getTestDialectTeaser('Transaction'), function() {
  beforeEach(function () {
    this.sinon = sinon.sandbox.create();
  });

  afterEach(function () {
    this.sinon.restore();
  });

  this.timeout(5000);
  describe('constructor', function() {
    it('stores options', function() {
      var transaction = new Transaction(this.sequelize);
      expect(transaction.options).to.be.an.instanceOf(Object);
    });

    it('generates an identifier', function() {
      var transaction = new Transaction(this.sequelize);
      expect(transaction.id).to.exist;
    });
  });

  describe('commit', function() {
    it('is a commit method available', function() {
      expect(Transaction).to.respondTo('commit');
    });
  });

  describe('rollback', function() {
    it('is a rollback method available', function() {
      expect(Transaction).to.respondTo('rollback');
    });
  });

  describe('autoCallback', function() {
    it('supports automatically committing', function() {
      return this.sequelize.transaction(function() {
        return Promise.resolve();
      });
    });

    it('supports automatically rolling back with a thrown error', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
        throw new Error('Yolo');
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    it('supports automatically rolling back with a rejection', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
        return Promise.reject(new Error('Swag'));
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    it('errors when no promise chain is returned', function() {
      var t;
      return (expect(this.sequelize.transaction(function(transaction) {
        t = transaction;
      })).to.eventually.be.rejected).then(function() {
        expect(t.finished).to.be.equal('rollback');
      });
    });

    if (dialect === 'postgres' || dialect === 'mssql') {
      it('do not rollback if already committed', function() {
        var SumSumSum = this.sequelize.define('transaction', {
              value: {
                type: Support.Sequelize.DECIMAL(10, 3),
                field: 'value'
              }
            })
          , self = this
          , transTest = function (val) {
              return self.sequelize.transaction({isolationLevel: 'SERIALIZABLE'}, function(t) {
                return SumSumSum.sum('value', {transaction: t}).then(function (balance) {
                  return SumSumSum.create({value: -val}, {transaction: t});
                });
              });
            };
        // Attention: this test is a bit racy. If you find a nicer way to test this: go ahead
        return SumSumSum.sync({force: true}).then(function () {
          return (expect(Promise.join(transTest(80), transTest(80), transTest(80))).to.eventually.be.rejectedWith('could not serialize access due to read/write dependencies among transactions'));
        }).delay(100).then(function() {
          if (self.sequelize.test.$runningQueries !== 0) {
            return self.sequelize.Promise.delay(200);
          }
          return void 0;
        }).then(function() {
          if (self.sequelize.test.$runningQueries !== 0) {
            return self.sequelize.Promise.delay(500);
          }
        });
      });
    }

  });

  it('does not allow queries after commit', function() {
    var self = this;
    return expect(
      this.sequelize.transaction().then(function(t) {
        return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true}).then(function() {
          return t.commit();
        }).then(function() {
          return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true});
        });
      })
    ).to.eventually.be.rejected;
  });

  it('does not allow queries after rollback', function() {
    var self = this;
    return expect(
      this.sequelize.transaction().then(function(t) {
        return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true}).then(function() {
          return t.rollback();
        }).then(function() {
          return self.sequelize.query('SELECT 1+1', {transaction: t, raw: true});
        });
      })
    ).to.eventually.be.rejected;
  });

  it('does not allow commits after commit', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.commit().then(function () {
        return t.commit();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be committed because it has been finished with state: commit');
  });

  it('does not allow commits after rollback', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.rollback().then(function () {
        return t.commit();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be committed because it has been finished with state: rollback');
  });

  it('does not allow rollbacks after commit', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.commit().then(function () {
        return t.rollback();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be rolled back because it has been finished with state: commit');
  });

  it('does not allow rollbacks after rollback', function () {
    var self = this;
    return expect(self.sequelize.transaction().then(function (t) {
      return t.rollback().then(function () {
        return t.rollback();
      });
    })).to.be.rejectedWith('Error: Transaction cannot be rolled back because it has been finished with state: rollback');
  });

  it('works even if a transaction: null option is passed', function () {
    this.sinon.spy(this.sequelize, 'query');

    return this.sequelize.transaction({
      transaction: null
    }).bind(this).then(function (t) {
      return t.commit().bind(this).then(function () {
        expect(this.sequelize.query.callCount).to.be.greaterThan(0);

        for (var i = 0; i < this.sequelize.query.callCount; i++) {
          expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t);
        }
      });
    });
  });

  it('works even if a transaction: undefined option is passed', function () {
    this.sinon.spy(this.sequelize, 'query');

    return this.sequelize.transaction({
      transaction: undefined
    }).bind(this).then(function (t) {
      return t.commit().bind(this).then(function () {
        expect(this.sequelize.query.callCount).to.be.greaterThan(0);

        for (var i = 0; i < this.sequelize.query.callCount; i++) {
          expect(this.sequelize.query.getCall(i).args[1].transaction).to.equal(t);
        }
      });
    });
  });

  if (dialect === 'sqlite'){
    it('provides persistent transactions', function () {
      var sequelize = new Support.Sequelize('database', 'username', 'password', {dialect: 'sqlite'})
        , User = sequelize.define('user', {
            username: Support.Sequelize.STRING,
            awesome: Support.Sequelize.BOOLEAN
          })
        , persistentTransaction;

      return sequelize.transaction().then(function(t) {
        return sequelize.sync({ transaction:t }).then(function( ) {
          return t;
        });
      }).then(function(t) {
        return User.create({}, {transaction:t}).then(function( ) {
          return t.commit();
        });
      }).then(function() {
        return sequelize.transaction().then(function(t) {
          persistentTransaction = t;
        });
      }).then(function() {
        return User.findAll({transaction: persistentTransaction}).then(function(users) {
          expect(users.length).to.equal(1);
          return persistentTransaction.commit();
        });
      });
    });
  }

  if (current.dialect.supports.transactionOptions.type) {
    describe('transaction types', function() {
      it('should support default transaction type DEFERRED', function() {
        return this.sequelize.transaction({
        }).bind(this).then(function (t) {
          return t.rollback().bind(this).then(function() {
            expect(t.options.type).to.equal('DEFERRED');
          });
        });
      });

      Object.keys(Transaction.TYPES).forEach(function(key) {
        it('should allow specification of ' + key + ' type', function() {
          return this.sequelize.transaction({
            type: key
          }).bind(this).then(function (t) {
            return t.rollback().bind(this).then(function() {
              expect(t.options.type).to.equal(Transaction.TYPES[key]);
            });
          });
        });
      });

    });

  }

  if (dialect === 'sqlite') {
    it('automatically retries on SQLITE_BUSY failure', function () {
      return Support.prepareTransactionTest(this.sequelize).bind({}).then(function(sequelize) {
        var User = sequelize.define('User', { username: Support.Sequelize.STRING });
        return User.sync({ force: true }).then(function() {
          var newTransactionFunc = function() {
            return sequelize.transaction({type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE}).then(function(t){
              return User.create({}, {transaction:t}).then(function( ) {
                return t.commit();
              });
            });
          };
          return Promise.join(newTransactionFunc(), newTransactionFunc()).then(function(results) {
            return User.findAll().then(function(users) {
              expect(users.length).to.equal(2);
            });
          });
        });
      });
    });

    it('fails with SQLITE_BUSY when retry.match is changed', function () {
      return Support.prepareTransactionTest(this.sequelize).bind({}).then(function(sequelize) {
        var User = sequelize.define('User', { id: {type: Support.Sequelize.INTEGER, primaryKey: true}, username: Support.Sequelize.STRING });
        return User.sync({ force: true }).then(function() {
          var newTransactionFunc = function() {
            return sequelize.transaction({type: Support.Sequelize.Transaction.TYPES.EXCLUSIVE, retry: {match: ['NO_MATCH']}}).then(function(t){
              // introduce delay to force the busy state race condition to fail
{'fraction_non_alphanumeric': 0.15505162241887904, 'fraction_numerical': 0.004056047197640118, 'mean_word_length': 2.6367750586657728, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4609453', 'n_tokens_mistral': 6270, 'n_tokens_neox': 6101, 'n_words': 1281}