// Licensed to Cloudera, Inc. under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. Cloudera, Inc. licenses this file // to you under the Apache License, Version 2.0 (the // "License"); you may not use this file except in compliance // with the License. You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { CancellablePromise } from '../../../api/cancellablePromise'; import Executor from '../../../apps/editor/execution/executor'; import SqlExecutable from './sqlExecutable'; import { ExecutionStatus } from './sqlExecutable'; import sessionManager from './sessionManager'; import * as ApiUtils from '../../../api/utils'; import { ParsedSqlStatement } from '../../../parse/sqlStatementsParser'; describe('sqlExecutable.js', () => { afterEach(() => { sessionManager.knownSessionPromises = {}; }); const mockExecutor = (mock: unknown): Executor => mock as Executor; const createParsedStatement = (statement: string, firstToken: string): ParsedSqlStatement => ({ location: { first_line: 0, first_column: 0, last_line: 1, last_column: 0 }, firstToken: firstToken, statement: statement, type: 'statement' }); const createSubject = ( statement: string | ParsedSqlStatement, limit?: number, dialect?: string ): SqlExecutable => { if (typeof statement === 'string') { return new SqlExecutable({ database: 'default', parsedStatement: createParsedStatement(statement, 'seleft'), executor: mockExecutor({ connector: () => ({ id: 'test', dialect: dialect || '' }), compute: () => ({ id: 'test' }), namespace: () => ({ id: 'test' }), defaultLimit: () => limit, toJs: () => ({}) }) }); } return new SqlExecutable({ database: 'default', parsedStatement: statement, executor: mockExecutor({ defaultLimit: () => limit }) }); }; const SELECT_STATEMENT = 'SELECT * FROM customers'; it('should handle strings statements', () => { const subject = createSubject(SELECT_STATEMENT); expect(subject.getStatement()).toEqual(SELECT_STATEMENT); }); it('should handle parsed statements', () => { const subject = createSubject(createParsedStatement(SELECT_STATEMENT, 'SELECT')); expect(subject.getStatement()).toEqual(SELECT_STATEMENT); }); it('should set the correct status after successful execute', async () => { const subject = createSubject(SELECT_STATEMENT, undefined, 'impala'); let createSessionApiHit = -1; let executeApiHit = -1; let checkStatusApiHit = -1; let getLogsApiHit = -1; let currentApiHit = 0; let logsResolve: (data: unknown) => void; const logsPromise = new CancellablePromise(resolve => { logsResolve = resolve; }); let statusResolve: (data: unknown) => void; const statusPromise = new CancellablePromise(resolve => { statusResolve = resolve; }); jest.spyOn(ApiUtils, 'post').mockImplementation((url: string): CancellablePromise => { currentApiHit++; if (url.indexOf('/create_session') !== -1) { createSessionApiHit = currentApiHit; return new CancellablePromise(resolve => { resolve({ session: { type: 'foo' } }); }); } else if (url.indexOf('/execute') !== -1) { executeApiHit = currentApiHit; expect(url).toContain('/execute/impala'); return new CancellablePromise(resolve => { resolve({ handle: {}, history_id: 1, history_uuid: 'some-history_uuid', history_parent_uuid: 'some_history_parent_uuid' }); }); } else if (url.indexOf('/check_status') !== -1) { checkStatusApiHit = currentApiHit; statusResolve({ query_status: { status: ExecutionStatus.available } }); return statusPromise; } else if (url.indexOf('/get_logs') !== -1) { getLogsApiHit = currentApiHit; logsResolve({ status: 0, logs: '' }); return logsPromise; } fail('fail for URL: ' + url); throw new Error('Did not find URL: ' + url); }); expect(subject.status).toEqual(ExecutionStatus.ready); await subject.execute(); await statusPromise; await logsPromise; expect(createSessionApiHit).toEqual(1); expect(executeApiHit).toEqual(2); expect(checkStatusApiHit).toEqual(3); expect(getLogsApiHit).toEqual(4); expect(subject.status).toEqual(ExecutionStatus.available); }); it('should update the handle on check status after execute', async () => { const subject = createSubject(SELECT_STATEMENT, undefined, 'impala'); let statusResolve: (data: unknown) => void; const statusPromise = new CancellablePromise(resolve => { statusResolve = resolve; }); jest.spyOn(ApiUtils, 'post').mockImplementation((url: string): CancellablePromise => { if (url.indexOf('/create_session') !== -1) { return CancellablePromise.resolve({ session: { type: 'foo' } }); } if (url.indexOf('/execute') !== -1) { expect(url).toContain('/execute/impala'); return CancellablePromise.resolve({ handle: { has_result_set: false }, history_id: 1, history_uuid: 'some-history_uuid', history_parent_uuid: 'some_history_parent_uuid' }); } if (url.indexOf('/check_status') !== -1) { statusResolve({ query_status: { status: ExecutionStatus.available, has_result_set: true } }); return statusPromise; } if (url.indexOf('/get_logs') !== -1) { return CancellablePromise.resolve({ status: 0, logs: '' }); } if (url.indexOf('/fetch_result_data')) { return CancellablePromise.resolve({}); } fail('fail for URL: ' + url); throw new Error('Did not find URL: ' + url); }); expect(subject.status).toEqual(ExecutionStatus.ready); await subject.execute(); expect(subject.status).not.toEqual(ExecutionStatus.available); expect(subject.handle?.has_result_set).toBeFalsy(); await statusPromise; expect(subject.status).toEqual(ExecutionStatus.available); expect(subject.handle?.has_result_set).toBeTruthy(); }); // xit('should set the correct status after failed execute', done => { // const subject = createSubject('SELECT * FROM customers'); // // const simplePostDeferred = $.Deferred(); // // jest.spyOn(ApiHelper, 'createSession').mockImplementation( // () => // new Promise(resolve => { // resolve({ type: 'x' }); // }) // ); // // jest.spyOn(ApiUtils, 'post').mockImplementation(url => { // expect(url).toEqual('/notebook/api/execute/impala'); // return simplePostDeferred; // }); // // subject // .execute() // .then(fail) // .catch(() => { // expect(subject.status).toEqual(ExecutionStatus.failed); // done(); // }); // // expect(subject.status).toEqual(ExecutionStatus.running); // // simplePostDeferred.reject(); // }); // xit('should set the correct status when cancelling', done => { // const subject = createSubject('SELECT * FROM customers'); // // const simplePostExeuteDeferred = $.Deferred(); // const simplePostCancelDeferred = $.Deferred(); // jest.spyOn(ApiUtils, 'post').mockImplementation(url => { // if (url === '/notebook/api/execute/impala') { // return simplePostExeuteDeferred; // } else if (url === '/notebook/api/cancel_statement') { // return simplePostCancelDeferred; // } // fail(); // }); // // subject // .execute() // .then(() => { // expect(subject.status).toEqual(ExecutionStatus.available); // subject.cancel().then(() => { // expect(subject.status).toEqual(ExecutionStatus.canceled); // done(); // }); // // expect(subject.status).toEqual(ExecutionStatus.canceling); // // simplePostCancelDeferred.resolve(); // }) // .catch(fail); // // expect(subject.status).toEqual(ExecutionStatus.running); // // simplePostExeuteDeferred.resolve({ handle: {} }); // }); });