//////////////////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2018, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////////////////

import * as subject from 'sources/sqleditor/execute_query';
import * as httpErrorHandler from 'sources/sqleditor/query_tool_http_error_handler';
import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import $ from 'jquery';

const context = describe;

describe('ExecuteQuery', () => {
  let sqlEditorMock;
  let networkMock;
  let executeQuery;
  let userManagementMock;
  let isNewTransactionRequiredMock;

  const startTime = new Date(2018, 1, 29, 12, 15, 52);
  beforeEach(() => {
    networkMock = new MockAdapter(axios);
    jasmine.addMatchers({jQuerytoHaveBeenCalledWith: jQuerytoHaveBeenCalledWith});
    userManagementMock = jasmine.createSpyObj('UserManagement', [
      'isPgaLoginRequired',
      'pgaLogin',
    ]);

    sqlEditorMock = jasmine.createSpyObj('SqlEditor', [
      'call_render_after_poll',
      'disable_tool_buttons',
      'resetQueryHistoryObject',
      'setIsQueryRunning',
      'trigger',
      'update_msg_history',
      '_highlight_error',
      '_init_polling_flags',
      'saveState',
      'initTransaction',
      'handle_connection_lost',
    ]);
    sqlEditorMock.transId = 123;
    sqlEditorMock.rows_affected = 1000;
    executeQuery = new subject.ExecuteQuery(sqlEditorMock, userManagementMock);
    isNewTransactionRequiredMock = spyOn(httpErrorHandler, 'httpResponseRequiresNewTransaction');
  });

  afterEach(() => {
    networkMock.restore();
  });

  describe('#poll', () => {
    let cancelButtonSpy;
    let response;

    beforeEach(() => {
      sqlEditorMock.POLL_FALLBACK_TIME = () => {
        return 0;
      };

      cancelButtonSpy = spyOn($.fn, 'prop');
      executeQuery.delayedPoll = jasmine.createSpy('ExecuteQuery.delayedPoll');
    });

    context('when SQLEditor is the query tool', () => {
      beforeEach(() => {
        sqlEditorMock.is_query_tool = true;
      });

      describe('when server answer with success', () => {
        describe('when query was successful', () => {
          beforeEach(() => {
            response = {
              data: {status: 'Success'},
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should update the loading icon message', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith(
                  'pgadmin-sqleditor:loading-icon:message',
                  'Loading data from the database server and rendering...'
                );
              done();
            }, 0);
          });

          it('should render the results', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.call_render_after_poll)
                .toHaveBeenCalledWith({status: 'Success'});
              done();
            }, 0);
          });
        });

        describe('when query is still running', () => {
          context('when no additional information is returned', () => {
            beforeEach(() => {
              response = {
                data: {status: 'Busy'},
              };

              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
              executeQuery.poll();
            });

            it('should set the flag to inform SQLEditor a query is running', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.setIsQueryRunning)
                  .toHaveBeenCalledWith(true);
                done();
              }, 0);
            });

            it('should does not update history', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.update_msg_history).not
                  .toHaveBeenCalled();
                done();
              }, 0);
            });

            it('should recursively call polling', (done) => {
              setTimeout(() => {
                expect(executeQuery.delayedPoll)
                  .toHaveBeenCalled();
                done();
              }, 0);
            });
          });

          context('when additional information is returned', () => {
            beforeEach(() => {
              response = {
                data: {
                  status: 'Busy',
                  result: 'Some important result',
                },
              };

              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
              executeQuery.poll();
            });

            it('should set the flag to inform SQLEditor a query is running', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.setIsQueryRunning)
                  .toHaveBeenCalledWith(true);
                done();
              }, 0);
            });

            it('should update history message', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith('Busy', 'Some important result', false);
                done();
              }, 0);
            });

            it('should recursively call polling', (done) => {
              setTimeout(() => {
                expect(executeQuery.delayedPoll)
                  .toHaveBeenCalled();
                done();
              }, 0);
            });
          });
        });

        describe('when the application lost connection with the database', () => {
          beforeEach(() => {
            response = {
              data: {
                status: 'NotConnected',
                result: 'Some interesting result',
              },
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should hide the loading icon', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
          });

          it('should add new entry to history and update the Messages tab and clear the result grid', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.update_msg_history)
                .toHaveBeenCalledWith(
                  false,
                  'Some interesting result',
                  true
                );
              done();
            }, 0);
          });

          it('should enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons)
                  .toHaveBeenCalledWith(false);
                done();
              }, 0);
          });

          it('should disable the cancel button', (done) => {
            setTimeout(
              () => {
                expect(cancelButtonSpy)
                  .toHaveBeenCalledWith('disabled', true);
                done();
              }, 0);
          });
        });

        describe('when query was cancelled', () => {
          beforeEach(() => {
            response = {
              data: {status: 'Cancel'},
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should hide the loading icon', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
          });

          it('should add new entry to history, add cancellation message to Messages tab and clear the result grid', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.update_msg_history)
                .toHaveBeenCalledWith(
                  false,
                  'Execution Cancelled!',
                  true
                );
              done();
            }, 0);
          });
        });
      });

      describe('when an error occur', () => {
        let errorMessageJson = {
          errormsg: 'Some error in JSON',
        };
        let errorMessageText = 'Some plain text error';

        describe('when the connection to the server was lost', () => {
          describe('when JSON response is available', () => {
            describe('when login is not required', () => {
              beforeEach(() => {
                userManagementMock.isPgaLoginRequired.and.returnValue(false);
                response = {responseJSON: errorMessageJson};
                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

                executeQuery.poll();
              });

              it('should hide the loading icon', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.trigger)
                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                    done();
                  }, 0);
              });

              it('should reset last query information', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.resetQueryHistoryObject)
                      .toHaveBeenCalledWith(sqlEditorMock);
                    done();
                  }, 0);
              });

              it('should highlight the error in the SQL panel', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock._highlight_error)
                      .toHaveBeenCalledWith('Some error in JSON');
                    done();
                  }, 0);
              });

              it('should add new entry to history and update the Messages tab', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.update_msg_history)
                      .toHaveBeenCalledWith(false, 'Some error in JSON');
                    done();
                  }, 0);
              });

              it('should enable the tool buttons', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.disable_tool_buttons)
                      .toHaveBeenCalledWith(false);
                    done();
                  }, 0);
              });

              it('should disable the cancel button', (done) => {
                setTimeout(
                  () => {
                    expect(cancelButtonSpy)
                      .toHaveBeenCalledWith('disabled', true);
                    done();
                  }, 0);
              });

              it('should not login is displayed', (done) => {
                setTimeout(
                  () => {
                    expect(userManagementMock.pgaLogin).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });
            });

            describe('when login is required', () => {
              beforeEach(() => {
                userManagementMock.isPgaLoginRequired.and.returnValue(true);
                response = {responseJSON: errorMessageJson};
                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

                executeQuery.poll();
              });

              it('should hide the loading icon', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.trigger)
                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                    done();
                  }, 0);
              });

              it('should reset last query information', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.resetQueryHistoryObject)
                      .toHaveBeenCalledWith(sqlEditorMock);
                    done();
                  }, 0);
              });

              it('should not highlight the error in the SQL panel', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock._highlight_error).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });

              it('should not add new entry to history and update the Messages tab', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.update_msg_history).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });

              it('should enable the tool buttons', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.disable_tool_buttons)
                      .toHaveBeenCalledWith(false);
                    done();
                  }, 0);
              });

              it('should disable the cancel button', (done) => {
                setTimeout(
                  () => {
                    expect(cancelButtonSpy)
                      .toHaveBeenCalledWith('disabled', true);
                    done();
                  }, 0);
              });

              it('should login is displayed', (done) => {
                setTimeout(
                  () => {
                    expect(userManagementMock.pgaLogin)
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });
            });
          });

          describe('when no JSON response is available', () => {
            describe('when login is not required', () => {
              beforeEach(() => {
                userManagementMock.isPgaLoginRequired.and.returnValue(false);
                response = {
                  errormsg: errorMessageText,
                };
                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

                executeQuery.poll();
              });

              it('should hide the loading icon', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.trigger)
                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                    done();
                  }, 0);
              });

              it('should reset last query information', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.resetQueryHistoryObject)
                      .toHaveBeenCalledWith(sqlEditorMock);
                    done();
                  }, 0);
              });

              it('should highlight the error in the SQL panel', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock._highlight_error)
                      .toHaveBeenCalledWith('Some plain text error');
                    done();
                  }, 0);
              });

              it('should add new entry to history and update the Messages tab', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.update_msg_history)
                      .toHaveBeenCalledWith(false, 'Some plain text error');
                    done();
                  }, 0);
              });

              it('should enable the tool buttons', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.disable_tool_buttons)
                      .toHaveBeenCalledWith(false);
                    done();
                  }, 0);
              });

              it('should disable the cancel button', (done) => {
                setTimeout(
                  () => {
                    expect(cancelButtonSpy)
                      .toHaveBeenCalledWith('disabled', true);
                    done();
                  }, 0);
              });

              it('should login is not displayed', (done) => {
                setTimeout(
                  () => {
                    expect(userManagementMock.pgaLogin).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });
            });

            describe('when login is required', () => {
              beforeEach(() => {
                userManagementMock.isPgaLoginRequired.and.returnValue(true);
                response = {
                  errormsg: errorMessageText,
                };
                networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

                executeQuery.poll();
              });

              it('should hide the loading icon', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.trigger)
                      .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                    done();
                  }, 0);
              });

              it('should reset last query information', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.resetQueryHistoryObject)
                      .toHaveBeenCalledWith(sqlEditorMock);
                    done();
                  }, 0);
              });

              it('should not highlight the error in the SQL panel', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock._highlight_error).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });

              it('should not add new entry to history and update the Messages tab', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.update_msg_history).not
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });

              it('should enable the tool buttons', (done) => {
                setTimeout(
                  () => {
                    expect(sqlEditorMock.disable_tool_buttons)
                      .toHaveBeenCalledWith(false);
                    done();
                  }, 0);
              });

              it('should disable the cancel button', (done) => {
                setTimeout(
                  () => {
                    expect(cancelButtonSpy)
                      .toHaveBeenCalledWith('disabled', true);
                    done();
                  }, 0);
              });

              it('should login is displayed', (done) => {
                setTimeout(
                  () => {
                    expect(userManagementMock.pgaLogin)
                      .toHaveBeenCalled();
                    done();
                  }, 0);
              });
            });
          });

          describe('when cannot reach the Python Server', () => {
            beforeEach(() => {
              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(404, undefined);

              executeQuery.poll();
            });

            it('should hide the loading icon', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.trigger)
                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                  done();
                }, 0);
            });

            it('should reset last query information', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.resetQueryHistoryObject)
                    .toHaveBeenCalledWith(sqlEditorMock);
                  done();
                }, 0);
            });

            it('should not highlight the error in the SQL panel', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock._highlight_error).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });

            it('should add new entry to history and update the Messages tab', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.update_msg_history)
                    .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
                  done();
                }, 0);
            });

            it('should enable the tool buttons', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.disable_tool_buttons)
                    .toHaveBeenCalledWith(false);
                  done();
                }, 0);
            });

            it('should disable the cancel button', (done) => {
              setTimeout(
                () => {
                  expect(cancelButtonSpy)
                    .toHaveBeenCalledWith('disabled', true);
                  done();
                }, 0);
            });

            it('should login is not displayed', (done) => {
              setTimeout(
                () => {
                  expect(userManagementMock.pgaLogin).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });
          });
        });
      });
    });

    context('when SQLEditor is NOT the query tool', () => {
      beforeEach(() => {
        sqlEditorMock.is_query_tool = false;
      });

      describe('when server answer with success', () => {
        describe('when query was successful', () => {
          beforeEach(() => {
            response = {
              data: {status: 'Success'},
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should update the loading icon message', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith(
                  'pgadmin-sqleditor:loading-icon:message',
                  'Loading data from the database server and rendering...'
                );
              done();
            }, 0);
          });

          it('should render the results', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.call_render_after_poll)
                .toHaveBeenCalledWith({status: 'Success'});
              done();
            }, 0);
          });
        });

        describe('when query is still running', () => {
          context('when no additional information is returned', () => {
            beforeEach(() => {
              response = {
                data: {status: 'Busy'},
              };

              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
              executeQuery.poll();
            });

            it('should set the flag to inform SQLEditor a query is running', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.setIsQueryRunning)
                  .toHaveBeenCalledWith(true);
                done();
              }, 0);
            });

            it('should does not update history', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.update_msg_history).not
                  .toHaveBeenCalled();
                done();
              }, 0);
            });

            it('should recursively call polling', (done) => {
              setTimeout(() => {
                expect(executeQuery.delayedPoll)
                  .toHaveBeenCalled();
                done();
              }, 0);
            });
          });

          context('when additional information is returned', () => {
            beforeEach(() => {
              response = {
                data: {
                  status: 'Busy',
                  result: 'Some important result',
                },
              };

              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);
              executeQuery.poll();
            });

            it('should set the flag to inform SQLEditor a query is running', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.setIsQueryRunning)
                  .toHaveBeenCalledWith(true);
                done();
              }, 0);
            });

            it('should update history message', (done) => {
              setTimeout(() => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith('Busy', 'Some important result', false);
                done();
              }, 0);
            });

            it('should recursively call polling', (done) => {
              setTimeout(() => {
                expect(executeQuery.delayedPoll)
                  .toHaveBeenCalled();
                done();
              }, 0);
            });
          });
        });

        describe('when the application lost connection with the database', () => {
          beforeEach(() => {
            response = {
              data: {
                status: 'NotConnected',
                result: 'Some interesting result',
              },
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should hide the loading icon', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
          });

          it('should add new entry to history and update the Messages tab and clear the result grid', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.update_msg_history)
                .toHaveBeenCalledWith(
                  false,
                  'Some interesting result',
                  true
                );
              done();
            }, 0);
          });

          it('should NOT enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });

          it('should NOT disable the cancel button', (done) => {
            setTimeout(
              () => {
                expect(cancelButtonSpy).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });
        });

        describe('when query was cancelled', () => {
          beforeEach(() => {
            response = {
              data: {status: 'Cancel'},
            };
            networkMock.onGet('/sqleditor/query_tool/poll/123').reply(200, response);

            executeQuery.poll();
          });

          it('should hide the loading icon', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
          });

          it('should add new entry to history, add cancellation message to Messages tab and clear the result grid', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.update_msg_history)
                .toHaveBeenCalledWith(
                  false,
                  'Execution Cancelled!',
                  true
                );
              done();
            }, 0);
          });
        });
      });

      describe('when an error occur', () => {
        let errorMessageJson = {
          errormsg: 'Some error in JSON',
        };
        let errorMessageText = 'Some plain text error';
        let response;

        describe('when the connection to the server was lost', () => {
          describe('when JSON response is available', () => {
            beforeEach(() => {
              response = {responseJSON: errorMessageJson};
              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

              executeQuery.poll();
            });

            it('should hide the loading icon', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.trigger)
                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                  done();
                }, 0);
            });

            it('should reset last query information', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.resetQueryHistoryObject)
                    .toHaveBeenCalledWith(sqlEditorMock);
                  done();
                }, 0);
            });

            it('should highlight the error in the SQL panel', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock._highlight_error)
                    .toHaveBeenCalledWith('Some error in JSON');
                  done();
                }, 0);
            });

            it('should add new entry to history and update the Messages tab', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.update_msg_history)
                    .toHaveBeenCalledWith(false, 'Some error in JSON');
                  done();
                }, 0);
            });

            it('should enable the tool buttons', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.disable_tool_buttons).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });

            it('should disable the cancel button', (done) => {
              setTimeout(
                () => {
                  expect(cancelButtonSpy).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });
          });
          describe('when no JSON response is available', () => {
            beforeEach(() => {
              response = {errormsg: errorMessageText};
              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(401, response);

              executeQuery.poll();
            });

            it('should hide the loading icon', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.trigger)
                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                  done();
                }, 0);
            });

            it('should reset last query information', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.resetQueryHistoryObject)
                    .toHaveBeenCalledWith(sqlEditorMock);
                  done();
                }, 0);
            });

            it('should highlight the error in the SQL panel', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock._highlight_error)
                    .toHaveBeenCalledWith('Some plain text error');
                  done();
                }, 0);
            });

            it('should add new entry to history and update the Messages tab', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.update_msg_history)
                    .toHaveBeenCalledWith(false, 'Some plain text error');
                  done();
                }, 0);
            });

            it('should not enable the tool buttons', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.disable_tool_buttons).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });

            it('should not disable the cancel button', (done) => {
              setTimeout(
                () => {
                  expect(cancelButtonSpy).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });
          });

          describe('when cannot reach the Python Server', () => {
            beforeEach(() => {
              networkMock.onGet('/sqleditor/query_tool/poll/123').reply(404, undefined);

              executeQuery.poll();
            });

            it('should hide the loading icon', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.trigger)
                    .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                  done();
                }, 0);
            });

            it('should reset last query information', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.resetQueryHistoryObject)
                    .toHaveBeenCalledWith(sqlEditorMock);
                  done();
                }, 0);
            });

            it('should not highlight the error in the SQL panel', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock._highlight_error).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });

            it('should add new entry to history and update the Messages tab', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.update_msg_history)
                    .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
                  done();
                }, 0);
            });

            it('should enable the tool buttons', (done) => {
              setTimeout(
                () => {
                  expect(sqlEditorMock.disable_tool_buttons).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });

            it('should disable the cancel button', (done) => {
              setTimeout(
                () => {
                  expect(cancelButtonSpy).not
                    .toHaveBeenCalled();
                  done();
                }, 0);
            });
          });
        });
      });
    });
  });

  describe('#execute', () => {
    let response;

    beforeEach(() => {
      response = {
        'info': '',
        'errormsg': '',
        'data': {
          'status': true,
          'can_edit': false,
          'info_notifier_timeout': 5,
          'result': '2',
          'can_filter': false,
        },
        'result': null,
        'success': 1,
      };
    });

    context('when the SQL statement is empty', () => {
      it('should return without executing', (done) => {
        let wasNetworkCalled = false;
        networkMock.onAny('/sqleditor/query_tool/start/123').reply(() => {
          wasNetworkCalled = true;
        });

        executeQuery.execute('', {});

        setTimeout(() => {
          expect(wasNetworkCalled).toBe(false);
          done();
        }, 0);
      });
    });

    context('when the SQL statement is not empty', () => {
      let pollSpy;
      let jqueryPropSpy;

      beforeEach(() => {
        jqueryPropSpy = spyOn($.fn, 'prop');
        sqlEditorMock.gridView = {};
        sqlEditorMock.gridView.query_tool_obj = jasmine.createSpyObj(
          'QueryToolObject',
          ['removeLineClass']
        );
      });

      describe('before the backend request', () => {
        beforeEach(() => {
          jasmine.clock().install();
          jasmine.clock().mockDate(startTime);
          jasmine.clock().tick(50);
          networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
          pollSpy = spyOn(executeQuery, 'poll');
          executeQuery.execute('some sql query', '');
        });

        afterEach(function () {
          jasmine.clock().uninstall();
        });

        it('should update loading text to "Initializing query execution"', () => {
          expect(sqlEditorMock.trigger)
            .toHaveBeenCalledWith(
              'pgadmin-sqleditor:loading-icon:show',
              'Initializing query execution...'
            );
        });

        it('disables the run query button', () => {
          const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-flash');

          expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
        });

        it('enable the cancel query button', () => {
          const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

          expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', false);
        });

        it('disable the query tool buttons', () => {
          expect(sqlEditorMock.disable_tool_buttons).toHaveBeenCalledWith(true);
        });

        it('initializes the polling flags', () => {
          expect(sqlEditorMock._init_polling_flags).toHaveBeenCalled();
        });

        it('save the query', () => {
          expect(sqlEditorMock.query).toEqual('some sql query');
        });

        it('reset the number of rows that were affected', () => {
          expect(sqlEditorMock.rows_affected).toBe(0);
        });

        it('reset query start time', () => {
          expect(sqlEditorMock.query_start_time.getTime()).toEqual(startTime.getTime() + 50);
        });
      });

      describe('when HTTP return 200', () => {
        describe('when backend informs that query started successfully', () => {
          beforeEach(() => {
            networkMock.onPost('/sqleditor/query_tool/start/123?connect=1').reply(200, response);
            pollSpy = spyOn(executeQuery, 'delayedPoll');
            executeQuery.execute('some sql query', '', true);
          });

          it('should changes the loading message to "Waiting for the query execution to complete"', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger).toHaveBeenCalledWith(
                'pgadmin-sqleditor:loading-icon:message',
                'Waiting for the query execution to complete...'
              );
              done();
            }, 0);
          });

          it('should update the can edit flag', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.can_edit).toBe(false);
              done();
            }, 0);
          });

          it('should update the can filter flag', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.can_filter).toBe(false);
              done();
            }, 0);
          });

          it('should update information notifier timeout', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.info_notifier_timeout).toBe(5);
              done();
            }, 0);
          });

          it('should start polling', (done) => {
            setTimeout(() => {
              expect(pollSpy).toHaveBeenCalled();
              done();
            }, 0);
          });
        });

        describe('when explain plan is not empty', () => {
          it('should send the explain plan informatioon through the wire', (done) => {
            networkMock.onAny('/sqleditor/query_tool/start/123').reply((config) => {
              setTimeout(() => {
                expect(config.data).toEqual(JSON.stringify({
                  sql: 'some sql query',
                  explain_plan: {
                    buffers: true,
                    analyze: false,
                    timing: true,
                    summary: false,
                  },
                }));

                done();
              }, 0);
              return [200, ''];
            });
            pollSpy = spyOn(executeQuery, 'delayedPoll');
            executeQuery.execute('some sql query', {
              buffers: true,
              analyze: false,
              timing: true,
              summary: false,
            });
          });


        });

        describe('when backend informs that there was a problem with the query', () => {
          beforeEach(() => {
            response.data.status = false;
            response.data.result = 'something went wrong';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
            pollSpy = spyOn(executeQuery, 'poll');
            executeQuery.execute('some sql query', '');
          });

          it('hide the loading screen', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.trigger).toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
          });

          it('disable the cancel query button', (done) => {
            setTimeout(() => {
              const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

              expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
              done();
            }, 0);
          });

          it('enable the query tool buttons', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.disable_tool_buttons).toHaveBeenCalledWith(false);
              done();
            }, 0);
          });

          it('update the history tab with the result message', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.update_msg_history).toHaveBeenCalledWith(false, 'something went wrong');
              done();
            }, 0);
          });

          it('highlight the error in the editor', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock._highlight_error).toHaveBeenCalledWith('something went wrong');
              done();
            }, 0);
          });

          it('should not start polling', (done) => {
            setTimeout(() => {
              expect(pollSpy).not.toHaveBeenCalled();
              done();
            }, 0);
          });
        });

        describe('when there is a marker set in the grid', () => {
          let markerClearSpy;
          beforeEach(() => {
            sqlEditorMock.gridView.marker = jasmine.createSpyObj(
              'GridViewMarker',
              ['clear']
            );
            markerClearSpy = sqlEditorMock.gridView.marker.clear;
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(200, response);
            pollSpy = spyOn(executeQuery, 'poll');
            executeQuery.execute('some sql query', '');
          });

          it('should call clear function on marker', (done) => {
            setTimeout(() => {
              expect(markerClearSpy).toHaveBeenCalled();
              done();
            }, 0);
          });

          it('should removes the marker', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.gridView.marker).toEqual(null);
              done();
            }, 0);
          });

          it('should remove CSS classes from the editor', (done) => {
            setTimeout(() => {
              expect(sqlEditorMock.gridView.query_tool_obj.removeLineClass)
                .toHaveBeenCalledWith(sqlEditorMock.marked_line_no, 'wrap', 'CodeMirror-activeline-background');
              done();
            }, 0);
          });
        });
      });

      describe('when cannot reach the Python Server', () => {
        beforeEach(() => {
          networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, undefined);


          executeQuery.execute('some sql query', '');
        });

        it('should hide the loading icon', (done) => {
          setTimeout(
            () => {
              expect(sqlEditorMock.trigger)
                .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
              done();
            }, 0);
        });

        it('should not highlight the error in the SQL panel', (done) => {
          setTimeout(
            () => {
              expect(sqlEditorMock._highlight_error).not
                .toHaveBeenCalled();
              done();
            }, 0);
        });

        it('should add new entry to history and update the Messages tab', (done) => {
          setTimeout(
            () => {
              expect(sqlEditorMock.update_msg_history)
                .toHaveBeenCalledWith(false, 'Not connected to the server or the connection to the server has been closed.');
              done();
            }, 0);
        });

        it('should enable the tool buttons', (done) => {
          setTimeout(
            () => {
              expect(sqlEditorMock.disable_tool_buttons)
                .toHaveBeenCalledWith(false);
              done();
            }, 0);
        });

        it('should disable the cancel button', (done) => {
          setTimeout(
            () => {
              const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

              expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
              done();
            }, 0);
        });
      });

      describe('when error is returned by the server', () => {
        describe('when login is not required', () => {
          beforeEach(() => {
            userManagementMock.isPgaLoginRequired.and.returnValue(false);
            response.errormsg = 'some error message';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);


            executeQuery.execute('some sql query', '');
          });

          it('should hide the loading icon', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.trigger)
                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                done();
              }, 0);
          });

          it('should not highlight the error in the SQL panel', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock._highlight_error).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });

          it('should add new entry to history and update the Messages tab', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith(false, 'some error message');
                done();
              }, 0);
          });

          it('should enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons)
                  .toHaveBeenCalledWith(false);
                done();
              }, 0);
          });

          it('should disable the cancel button', (done) => {
            setTimeout(
              () => {
                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
                done();
              }, 0);
          });

          it('should not save the state', () => {
            setTimeout(() => {
              expect(sqlEditorMock.saveState).not.toHaveBeenCalled();
            }, 0);
          });

          it('should not display pga login', () => {
            setTimeout(() => {
              expect(userManagementMock.pgaLogin).not.toHaveBeenCalled();
            }, 0);
          });
        });
        describe('when login is required', () => {
          beforeEach(() => {
            userManagementMock.isPgaLoginRequired.and.returnValue(true);
            response.errormsg = 'some error message';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);


            executeQuery.execute('some sql query', '');
          });

          it('should hide the loading icon', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.trigger)
                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                done();
              }, 0);
          });

          it('should not highlight the error in the SQL panel', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock._highlight_error).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });

          it('should add new entry to history and update the Messages tab', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith(false, 'some error message');
                done();
              }, 0);
          });

          it('should enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons)
                  .toHaveBeenCalledWith(false);
                done();
              }, 0);
          });

          it('should disable the cancel button', (done) => {
            setTimeout(
              () => {
                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
                done();
              }, 0);
          });

          it('should save the state', () => {
            setTimeout(() => {
              expect(sqlEditorMock.saveState).toHaveBeenCalledWith(
                'execute',
                ['']
              );
            }, 0);
          });

          it('should display pga login', () => {
            setTimeout(() => {
              expect(userManagementMock.pgaLogin).toHaveBeenCalled();
            }, 0);
          });
        });
        describe('when a new transaction is not required', () => {
          beforeEach(() => {
            isNewTransactionRequiredMock.and.returnValue(false);
            response.errormsg = 'some error message';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);


            executeQuery.execute('some sql query', '');
          });

          it('should hide the loading icon', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.trigger)
                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                done();
              }, 0);
          });

          it('should not highlight the error in the SQL panel', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock._highlight_error).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });

          it('should add new entry to history and update the Messages tab', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith(false, 'some error message');
                done();
              }, 0);
          });

          it('should enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons)
                  .toHaveBeenCalledWith(false);
                done();
              }, 0);
          });

          it('should disable the cancel button', (done) => {
            setTimeout(
              () => {
                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
                done();
              }, 0);
          });

          it('should not save the state', () => {
            setTimeout(() => {
              expect(sqlEditorMock.saveState).not.toHaveBeenCalled();
            }, 0);
          });

          it('should not display pga login', () => {
            setTimeout(() => {
              expect(userManagementMock.pgaLogin).not.toHaveBeenCalled();
            }, 0);
          });

          it('should not initialize a new transaction', () => {
            setTimeout(() => {
              expect(sqlEditorMock.initTransaction).not.toHaveBeenCalled();
            }, 0);
          });
        });

        describe('when a new transaction is required', () => {
          beforeEach(() => {
            isNewTransactionRequiredMock.and.returnValue(true);
            response.errormsg = 'some error message';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(500, response);

            executeQuery.execute('some sql query', '');
          });

          it('should hide the loading icon', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.trigger)
                  .toHaveBeenCalledWith('pgadmin-sqleditor:loading-icon:hide');
                done();
              }, 0);
          });

          it('should not highlight the error in the SQL panel', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock._highlight_error).not
                  .toHaveBeenCalled();
                done();
              }, 0);
          });

          it('should add new entry to history and update the Messages tab', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.update_msg_history)
                  .toHaveBeenCalledWith(false, 'some error message');
                done();
              }, 0);
          });

          it('should enable the tool buttons', (done) => {
            setTimeout(
              () => {
                expect(sqlEditorMock.disable_tool_buttons)
                  .toHaveBeenCalledWith(false);
                done();
              }, 0);
          });

          it('should disable the cancel button', (done) => {
            setTimeout(
              () => {
                const callToProp = findJQueryCallWithSelector(jqueryPropSpy, '#btn-cancel-query');

                expect(callToProp).jQuerytoHaveBeenCalledWith('disabled', true);
                done();
              }, 0);
          });

          it('should save the state', () => {
            setTimeout(() => {
              expect(sqlEditorMock.saveState).toHaveBeenCalledWith(
                'execute',
                ['']
              );
            }, 0);
          });

          it('should not display pga login', () => {
            setTimeout(() => {
              expect(userManagementMock.pgaLogin).not.toHaveBeenCalled();
            }, 0);
          });

          it('should initialize a new transaction', () => {
            setTimeout(() => {
              expect(sqlEditorMock.initTransaction).toHaveBeenCalled();
            }, 0);
          });
        });
        describe('when connection to database is lost', () => {
          beforeEach(() => {
            isNewTransactionRequiredMock.and.returnValue(false);
            response.info = 'CONNECTION_LOST';
            networkMock.onAny('/sqleditor/query_tool/start/123').reply(503, response);

            executeQuery.execute('some sql query', '');
          });

          it('saves state', () => {
            setTimeout(() => {
              expect(sqlEditorMock.saveState).toHaveBeenCalledWith(
                'execute',
                ['']
              );
            }, 0);
          });

          it('calls handle_connection_lost', () => {
            setTimeout(() => {
              expect(sqlEditorMock.handle_connection_lost).toHaveBeenCalled();
            }, 0);
          });
        });
      });

    });
  });

  let findJQueryCallWithSelector = (jquerySpy, selector) => {
    let result = undefined;

    jquerySpy.calls.all().forEach((call) => {
      if (call.object.selector === selector) {
        result = call;
      }
    });
    return result;
  };
});

const jQuerytoHaveBeenCalledWith = function (util) {
  return {
    compare: function (actual) {
      let result = {};
      let expectedArgs = jasmine.util.argsToArray(arguments).slice(1);
      if (actual.object === undefined || actual.object.selector === undefined) {
        throw new Error('Expected a JQuery object, but got ' + jasmine.pp(actual) + '.');
      }

      result.pass = util.equals(actual.args, expectedArgs, '');
      if (result.pass) {
        result.message = 'larifo';
      } else {
        result.message =
          'Expected jquery with selector "' +
          actual.object.selector +
          '" to have been called with ' +
          jasmine.pp(expectedArgs) +
          ' but was called with ' +
          jasmine.pp(actual.args);
      }

      return result;
    },
  };
};

