570 lines
21 KiB
JavaScript
570 lines
21 KiB
JavaScript
/*
|
|
* (c) Copyright Ascensio System SIA 2010-2024
|
|
*
|
|
* This program is a free software product. You can redistribute it and/or
|
|
* modify it under the terms of the GNU Affero General Public License (AGPL)
|
|
* version 3 as published by the Free Software Foundation. In accordance with
|
|
* Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
|
|
* that Ascensio System SIA expressly excludes the warranty of non-infringement
|
|
* of any third-party rights.
|
|
*
|
|
* This program is distributed WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For
|
|
* details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
|
*
|
|
* You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
|
|
* street, Riga, Latvia, EU, LV-1050.
|
|
*
|
|
* The interactive user interfaces in modified source and object code versions
|
|
* of the Program must display Appropriate Legal Notices, as required under
|
|
* Section 5 of the GNU AGPL version 3.
|
|
*
|
|
* Pursuant to Section 7(b) of the License you must retain the original Product
|
|
* logo when distributing the program. Pursuant to Section 7(e) we decline to
|
|
* grant you any rights under trademark law for use of our trademarks.
|
|
*
|
|
* All the Product's GUI elements, including illustrations and icon sets, as
|
|
* well as technical writing content are licensed under the terms of the
|
|
* Creative Commons Attribution-ShareAlike 4.0 International. See the License
|
|
* terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
|
*
|
|
*/
|
|
|
|
const {describe, test, expect, afterAll} = require('@jest/globals');
|
|
const config = require('../../../Common/node_modules/config');
|
|
|
|
const baseConnector = require('../../../DocService/sources/databaseConnectors/baseConnector');
|
|
const operationContext = require('../../../Common/sources/operationContext');
|
|
const taskResult = require('../../../DocService/sources/taskresult');
|
|
const commonDefines = require('../../../Common/sources/commondefines');
|
|
const constants = require('../../../Common/sources/constants');
|
|
const utils = require('../../../Common/sources/utils');
|
|
const configSql = config.get('services.CoAuthoring.sql');
|
|
|
|
const ctx = new operationContext.Context();
|
|
const cfgDbType = configSql.get('type');
|
|
const cfgTableResult = configSql.get('tableResult');
|
|
const cfgTableChanges = configSql.get('tableChanges');
|
|
const dbTypes = {
|
|
oracle: {
|
|
number: 'NUMBER',
|
|
string: 'NVARCHAR(50)'
|
|
},
|
|
mssql: {
|
|
number: 'INT',
|
|
string: 'NVARCHAR(50)'
|
|
},
|
|
mysql: {
|
|
number: 'INT',
|
|
string: 'VARCHAR(50)'
|
|
},
|
|
dameng: {
|
|
number: 'INT',
|
|
string: 'VARCHAR(50)'
|
|
},
|
|
postgres: {
|
|
number: 'INT',
|
|
string: 'VARCHAR(50)'
|
|
},
|
|
number() {
|
|
return this[cfgDbType].number;
|
|
},
|
|
string() {
|
|
return this[cfgDbType].string;
|
|
}
|
|
};
|
|
|
|
const insertCases = {
|
|
5: 'baseConnector-insert()-tester-5-rows',
|
|
500: 'baseConnector-insert()-tester-500-rows',
|
|
1000: 'baseConnector-insert()-tester-1000-rows',
|
|
5000: 'baseConnector-insert()-tester-5000-rows',
|
|
10000: 'baseConnector-insert()-tester-10000-rows'
|
|
};
|
|
const changesCases = {
|
|
range: 'baseConnector-getChangesPromise()-tester',
|
|
index: 'baseConnector-getChangesIndexPromise()-tester',
|
|
delete: 'baseConnector-deleteChangesPromise()-tester'
|
|
};
|
|
const emptyCallbacksCase = [
|
|
'baseConnector-getEmptyCallbacks()-tester-0',
|
|
'baseConnector-getEmptyCallbacks()-tester-1',
|
|
'baseConnector-getEmptyCallbacks()-tester-2',
|
|
'baseConnector-getEmptyCallbacks()-tester-3',
|
|
'baseConnector-getEmptyCallbacks()-tester-4'
|
|
];
|
|
const documentsWithChangesCase = ['baseConnector-getDocumentsWithChanges()-tester-0', 'baseConnector-getDocumentsWithChanges()-tester-1'];
|
|
const getExpiredCase = ['baseConnector-getExpired()-tester-0', 'baseConnector-getExpired()-tester-1', 'baseConnector-getExpired()-tester-2'];
|
|
const getCountWithStatusCase = ['baseConnector-getCountWithStatusCase()-tester-0'];
|
|
const upsertCases = {
|
|
insert: 'baseConnector-upsert()-tester-row-inserted',
|
|
update: 'baseConnector-upsert()-tester-row-updated'
|
|
};
|
|
const updateIfCases = {
|
|
notEmpty: 'baseConnector-updateIf()-tester-not-empty-callback',
|
|
emptyCallback: 'baseConnector-updateIf()-tester-empty-callback'
|
|
};
|
|
const oracleNullHandlingCases = ['baseConnector-oracle-nclob-null-handling', 'baseConnector-oracle-nclob-null-handling-2'];
|
|
|
|
function createChanges(changesLength, date) {
|
|
const objChanges = [
|
|
{
|
|
docid: '__ffff_127.0.0.1new.docx41692082262909',
|
|
change: '"64;AgAAADEA//8BAG+X6xGnEAMAjgAAAAIAAAAEAAAABAAAAAUAAACCAAAAggAAAA4AAAAwAC4AMAAuADAALgAwAA=="',
|
|
time: date,
|
|
user: 'uid-18',
|
|
useridoriginal: 'uid-1'
|
|
}
|
|
];
|
|
|
|
const length = changesLength - 1;
|
|
for (let i = 1; i <= length; i++) {
|
|
objChanges.push({
|
|
docid: '__ffff_127.0.0.1new.docx41692082262909',
|
|
change: '"39;CgAAADcAXwA2ADQAMAACABwAAQAAAAAAAAABAAAALgAAAAAAAAAA"',
|
|
time: date,
|
|
user: 'uid-18',
|
|
useridoriginal: 'uid-1'
|
|
});
|
|
}
|
|
|
|
return objChanges;
|
|
}
|
|
|
|
async function getRowsCountById(table, id) {
|
|
const result = await executeSql(`SELECT COUNT(id) AS count FROM ${table} WHERE id = '${id}';`);
|
|
// Return type of COUNT() in postgres is bigint which treats as string by connector. Dameng DB returns js bigint type.
|
|
return Number(result[0].count);
|
|
}
|
|
|
|
async function noRowsExistenceCheck(table, id) {
|
|
const noRows = await getRowsCountById(table, id);
|
|
expect(noRows).toEqual(0);
|
|
}
|
|
|
|
function deleteRowsByIds(table, ids) {
|
|
const idToDelete = ids.map(id => `id = '${id}'`).join(' OR ');
|
|
return executeSql(`DELETE FROM ${table} WHERE ${idToDelete};`);
|
|
}
|
|
|
|
function executeSql(sql, values = []) {
|
|
return new Promise((resolve, reject) => {
|
|
baseConnector.sqlQuery(
|
|
ctx,
|
|
sql,
|
|
(error, result) => {
|
|
if (error) {
|
|
reject(error);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
},
|
|
false,
|
|
false,
|
|
values
|
|
);
|
|
});
|
|
}
|
|
|
|
function createTask(id, callback = '', baseurl = '') {
|
|
const task = new taskResult.TaskResultData();
|
|
task.tenant = ctx.tenant;
|
|
task.key = id;
|
|
task.status = commonDefines.FileStatus.None;
|
|
task.statusInfo = constants.NO_ERROR;
|
|
task.callback = callback;
|
|
task.baseurl = baseurl;
|
|
task.completeDefaults();
|
|
|
|
return task;
|
|
}
|
|
|
|
function insertIntoResultTable(dateNow, task) {
|
|
let cbInsert = task.callback;
|
|
if (task.callback) {
|
|
const userCallback = new baseConnector.UserCallback();
|
|
userCallback.fromValues(task.userIndex, task.callback);
|
|
cbInsert = userCallback.toSQLInsert();
|
|
}
|
|
|
|
const columns = ['tenant', 'id', 'status', 'status_info', 'last_open_date', 'user_index', 'change_id', 'callback', 'baseurl'];
|
|
const values = [];
|
|
const placeholder = [
|
|
baseConnector.addSqlParameter(task.tenant, values),
|
|
baseConnector.addSqlParameter(task.key, values),
|
|
baseConnector.addSqlParameter(task.status, values),
|
|
baseConnector.addSqlParameter(task.statusInfo, values),
|
|
baseConnector.addSqlParameter(dateNow, values),
|
|
baseConnector.addSqlParameter(task.userIndex, values),
|
|
baseConnector.addSqlParameter(task.changeId, values),
|
|
baseConnector.addSqlParameter(cbInsert, values),
|
|
baseConnector.addSqlParameter(task.baseurl, values)
|
|
];
|
|
|
|
return executeSql(`INSERT INTO ${cfgTableResult}(${columns.join(', ')}) VALUES(${placeholder.join(', ')});`, values);
|
|
}
|
|
|
|
afterAll(async () => {
|
|
const insertIds = Object.values(insertCases);
|
|
const changesIds = Object.values(changesCases);
|
|
const upsertIds = Object.values(upsertCases);
|
|
const updateIfIds = Object.values(updateIfCases);
|
|
|
|
const tableChangesIds = [...emptyCallbacksCase, ...documentsWithChangesCase, ...changesIds, ...insertIds];
|
|
const tableResultIds = [
|
|
...emptyCallbacksCase,
|
|
...documentsWithChangesCase,
|
|
...getExpiredCase,
|
|
...getCountWithStatusCase,
|
|
...upsertIds,
|
|
...updateIfIds,
|
|
...oracleNullHandlingCases
|
|
];
|
|
|
|
const deletionPool = [
|
|
deleteRowsByIds(cfgTableChanges, tableChangesIds),
|
|
deleteRowsByIds(cfgTableResult, tableResultIds),
|
|
executeSql('DROP TABLE test_table;')
|
|
];
|
|
|
|
await Promise.allSettled(deletionPool);
|
|
baseConnector.closePool?.();
|
|
}, 10000); //default timeout is 5000ms. increased to 10000ms to prevent timeout on Oracle DB
|
|
|
|
// Assumed that at least default DB was installed and configured.
|
|
describe('Base database connector', () => {
|
|
test('Availability of configured DB', async () => {
|
|
const result = await baseConnector.healthCheck(ctx);
|
|
|
|
expect(result.length).toEqual(1);
|
|
});
|
|
|
|
test('Correct return format of requested rows', async () => {
|
|
const result = await baseConnector.healthCheck(ctx);
|
|
|
|
// The [[constructor]] field is referring to a parent class instance, so for Object-like values it is equal to itself.
|
|
expect(result.constructor).toEqual(Array);
|
|
// SQL in healthCheck() request column with value 1, so we expect only one value. The default format, that used here is [{ columnName: columnValue }, { columnName: columnValue }].
|
|
expect(result.length).toEqual(1);
|
|
expect(result[0].constructor).toEqual(Object);
|
|
expect(Object.values(result[0]).length).toEqual(1);
|
|
// Value itself.
|
|
expect(Object.values(result[0])[0]).toEqual(1);
|
|
});
|
|
|
|
test('Correct return format of changing in DB', async () => {
|
|
const createTableSql = `CREATE TABLE test_table(num ${dbTypes.number()});`;
|
|
const alterTableSql = `INSERT INTO test_table VALUES(1);`;
|
|
|
|
await executeSql(createTableSql);
|
|
const result = await executeSql(alterTableSql);
|
|
|
|
expect(result).toEqual({affectedRows: 1});
|
|
});
|
|
|
|
describe('DB tables existence', () => {
|
|
const tables = {
|
|
[cfgTableResult]: constants.TABLE_RESULT_SCHEMA.map(column => {
|
|
return {column_name: column};
|
|
}),
|
|
[cfgTableChanges]: constants.TABLE_CHANGES_SCHEMA.map(column => {
|
|
return {column_name: column};
|
|
})
|
|
};
|
|
|
|
for (const table in tables) {
|
|
test(`${table} table existence`, async () => {
|
|
const result = await baseConnector.getTableColumns(ctx, table);
|
|
for (const row of tables[table]) {
|
|
expect(result).toContainEqual(row);
|
|
}
|
|
});
|
|
}
|
|
|
|
const table = 'unused_table';
|
|
test(`${table} table absence`, async () => {
|
|
const result = await baseConnector.getTableColumns(ctx, table);
|
|
expect(result).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Changes manipulations', () => {
|
|
const date = new Date();
|
|
const index = 0;
|
|
const user = {
|
|
id: 'uid-18',
|
|
idOriginal: 'uid-1',
|
|
username: 'John Smith',
|
|
indexUser: 8,
|
|
view: false
|
|
};
|
|
|
|
describe('Add changes', () => {
|
|
for (const testCase in insertCases) {
|
|
// Increase timeout for large inserts (5000+ rows can take longer on some databases)
|
|
const timeout = +testCase >= 5000 ? 15000 : 5000;
|
|
test(
|
|
`${testCase} rows inserted`,
|
|
async () => {
|
|
const docId = insertCases[testCase];
|
|
const objChanges = createChanges(+testCase, date);
|
|
|
|
await noRowsExistenceCheck(cfgTableChanges, docId);
|
|
|
|
await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user);
|
|
const result = await getRowsCountById(cfgTableChanges, docId);
|
|
|
|
expect(result).toEqual(objChanges.length);
|
|
},
|
|
timeout
|
|
);
|
|
}
|
|
});
|
|
|
|
describe('Get and delete changes', () => {
|
|
const changesCount = 10;
|
|
const objChanges = createChanges(changesCount, date);
|
|
|
|
test('Get changes in range', async () => {
|
|
const docId = changesCases.range;
|
|
const additionalChangesCount = 5;
|
|
const dayBefore = new Date();
|
|
dayBefore.setDate(dayBefore.getDate() - 1);
|
|
const limitedByDateChanges = createChanges(additionalChangesCount, dayBefore);
|
|
const fullChanges = [...objChanges, ...limitedByDateChanges];
|
|
|
|
await noRowsExistenceCheck(cfgTableChanges, docId);
|
|
|
|
await baseConnector.insertChangesPromise(ctx, fullChanges, docId, index, user);
|
|
|
|
const result = await baseConnector.getChangesPromise(ctx, docId, index, changesCount);
|
|
expect(result.length).toEqual(changesCount);
|
|
|
|
dayBefore.setSeconds(dayBefore.getSeconds() + 1);
|
|
const resultByDate = await baseConnector.getChangesPromise(ctx, docId, index, changesCount + additionalChangesCount, dayBefore);
|
|
expect(resultByDate.length).toEqual(additionalChangesCount);
|
|
});
|
|
|
|
test('Get changes index', async () => {
|
|
const docId = changesCases.index;
|
|
|
|
await noRowsExistenceCheck(cfgTableChanges, docId);
|
|
|
|
await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user);
|
|
|
|
const result = await baseConnector.getChangesIndexPromise(ctx, docId);
|
|
|
|
// We created 10 changes rows, change_id: 0..9, changes index is MAX(change_id).
|
|
const expected = [{change_id: 9}];
|
|
expect(result).toEqual(expected);
|
|
});
|
|
|
|
test('Delete changes', async () => {
|
|
const docId = changesCases.delete;
|
|
|
|
await baseConnector.insertChangesPromise(ctx, objChanges, docId, index, user);
|
|
|
|
// Deleting 6 rows.
|
|
await baseConnector.deleteChangesPromise(ctx, docId, 4);
|
|
|
|
const result = await getRowsCountById(cfgTableChanges, docId);
|
|
|
|
// Rest rows.
|
|
expect(result).toEqual(4);
|
|
});
|
|
});
|
|
|
|
test('Get empty callbacks', async () => {
|
|
const idCount = 5;
|
|
const notNullCallbacks = idCount - 2;
|
|
|
|
const resultBefore = await baseConnector.getEmptyCallbacks(ctx);
|
|
|
|
// Adding non-empty callbacks.
|
|
for (let i = 0; i < notNullCallbacks; i++) {
|
|
const task = createTask(emptyCallbacksCase[i], 'some_callback');
|
|
await insertIntoResultTable(date, task);
|
|
}
|
|
|
|
// Adding empty callbacks.
|
|
for (let i = notNullCallbacks; i < idCount; i++) {
|
|
const task = createTask(emptyCallbacksCase[i], '');
|
|
await insertIntoResultTable(date, task);
|
|
}
|
|
|
|
// Adding same amount of changes with same tenant and id.
|
|
const objChanges = createChanges(1, date);
|
|
for (let i = 0; i < idCount; i++) {
|
|
await baseConnector.insertChangesPromise(ctx, objChanges, emptyCallbacksCase[i], index, user);
|
|
}
|
|
|
|
const resultAfter = await baseConnector.getEmptyCallbacks(ctx);
|
|
|
|
expect(resultAfter.length).toEqual(resultBefore.length + idCount - notNullCallbacks);
|
|
});
|
|
|
|
test('Get documents with changes', async () => {
|
|
const objChanges = createChanges(1, date);
|
|
|
|
const resultBeforeNewRows = await baseConnector.getDocumentsWithChanges(ctx);
|
|
|
|
for (const id of documentsWithChangesCase) {
|
|
const task = createTask(id);
|
|
await Promise.all([baseConnector.insertChangesPromise(ctx, objChanges, id, index, user), insertIntoResultTable(date, task)]);
|
|
}
|
|
|
|
const resultAfterNewRows = await baseConnector.getDocumentsWithChanges(ctx);
|
|
expect(resultAfterNewRows.length).toEqual(resultBeforeNewRows.length + documentsWithChangesCase.length);
|
|
});
|
|
|
|
test('Get expired', async () => {
|
|
const maxCount = 100;
|
|
const dayBefore = new Date();
|
|
dayBefore.setDate(dayBefore.getDate() - 1);
|
|
|
|
const resultBeforeNewRows = await baseConnector.getExpired(ctx, maxCount, 0);
|
|
|
|
for (const id of getExpiredCase) {
|
|
const task = createTask(id);
|
|
await insertIntoResultTable(dayBefore, task);
|
|
}
|
|
|
|
// 3 rows were added.
|
|
const resultAfterNewRows = await baseConnector.getExpired(ctx, maxCount + 3, 0);
|
|
|
|
expect(resultAfterNewRows.length).toEqual(resultBeforeNewRows.length + getExpiredCase.length);
|
|
});
|
|
|
|
test('Get Count With Status', async () => {
|
|
let countWithStatus;
|
|
const unknownStatus = 99; //to avoid collision with running server
|
|
const EXEC_TIMEOUT = 30000 + utils.getConvertionTimeout(undefined);
|
|
countWithStatus = await baseConnector.getCountWithStatus(ctx, unknownStatus, EXEC_TIMEOUT);
|
|
expect(countWithStatus).toEqual(0);
|
|
for (const id of getCountWithStatusCase) {
|
|
const task = createTask(id);
|
|
task.status = unknownStatus;
|
|
await insertIntoResultTable(date, task);
|
|
}
|
|
countWithStatus = await baseConnector.getCountWithStatus(ctx, unknownStatus, EXEC_TIMEOUT);
|
|
expect(countWithStatus).toEqual(getCountWithStatusCase.length);
|
|
});
|
|
});
|
|
|
|
describe('upsert() method', () => {
|
|
test('New row inserted', async () => {
|
|
const task = createTask(upsertCases.insert);
|
|
|
|
await noRowsExistenceCheck(cfgTableResult, task.key);
|
|
|
|
const result = await baseConnector.upsert(ctx, task);
|
|
|
|
// isInsert should be true because of insert operation, insertId should be 1 by default.
|
|
const expected = {isInsert: true, insertId: 1};
|
|
expect(result).toEqual(expected);
|
|
|
|
const insertedResult = await getRowsCountById(cfgTableResult, task.key);
|
|
|
|
expect(insertedResult).toEqual(1);
|
|
});
|
|
|
|
test('Row updated', async () => {
|
|
const task = createTask(upsertCases.update, '', 'some-url');
|
|
|
|
await noRowsExistenceCheck(cfgTableResult, task.key);
|
|
|
|
await baseConnector.upsert(ctx, task);
|
|
|
|
// Changing baseurl to verify upsert() changing the row.
|
|
task.baseurl = 'some-updated-url';
|
|
const result = await baseConnector.upsert(ctx, task);
|
|
|
|
// isInsert should be false because of update operation, insertId should be 2 by updating clause.
|
|
const expected = {isInsert: false, insertId: 2};
|
|
expect(result).toEqual(expected);
|
|
|
|
const updatedRow = await executeSql(`SELECT id, baseurl FROM ${cfgTableResult} WHERE id = '${task.key}';`);
|
|
|
|
const expectedUrlChanges = [{id: task.key, baseurl: 'some-updated-url'}];
|
|
expect(updatedRow).toEqual(expectedUrlChanges);
|
|
});
|
|
});
|
|
|
|
describe('Oracle NCLOB null handling', () => {
|
|
const nullHandlingCase = 'baseConnector-oracle-nclob-null-handling';
|
|
|
|
test('Empty callback is retrieved as empty string (not null)', async () => {
|
|
const date = new Date();
|
|
const task = createTask(nullHandlingCase, ''); // Empty callback
|
|
|
|
await noRowsExistenceCheck(cfgTableResult, task.key);
|
|
await insertIntoResultTable(date, task);
|
|
|
|
// Retrieve the row and check callback field
|
|
const result = await executeSql(`SELECT callback, baseurl FROM ${cfgTableResult} WHERE id = '${task.key}';`);
|
|
|
|
expect(result.length).toEqual(1);
|
|
// Oracle should normalize null NCLOB to empty string
|
|
expect(result[0].callback).toEqual('');
|
|
expect(result[0].baseurl).toEqual('');
|
|
// Verify they are strings, not null
|
|
expect(typeof result[0].callback).toEqual('string');
|
|
expect(typeof result[0].baseurl).toEqual('string');
|
|
});
|
|
|
|
test('Null callback does not cause TypeError in getCallbackByUserIndex', async () => {
|
|
const date = new Date();
|
|
const task = createTask(nullHandlingCase + '-2', '');
|
|
|
|
await insertIntoResultTable(date, task);
|
|
|
|
const result = await executeSql(`SELECT callback FROM ${cfgTableResult} WHERE id = '${task.key}';`);
|
|
|
|
// This should not throw TypeError
|
|
const userCallback = new baseConnector.UserCallback();
|
|
expect(() => {
|
|
userCallback.getCallbackByUserIndex(ctx, result[0].callback, 1);
|
|
}).not.toThrow();
|
|
|
|
const callbackResult = userCallback.getCallbackByUserIndex(ctx, result[0].callback, 1);
|
|
expect(callbackResult).toEqual('');
|
|
});
|
|
});
|
|
|
|
describe('updateIf() method', () => {
|
|
test('Update with NOT_EMPTY callback mask', async () => {
|
|
const date = new Date();
|
|
const taskWithCallback = createTask(updateIfCases.notEmpty, 'http://example.com/callback');
|
|
const taskEmptyCallback = createTask(updateIfCases.emptyCallback, '');
|
|
|
|
// Insert two rows: one with callback, one without
|
|
await Promise.all([insertIntoResultTable(date, taskWithCallback), insertIntoResultTable(date, taskEmptyCallback)]);
|
|
|
|
// Update mask: only update rows with non-empty callback and status=None
|
|
const mask = new taskResult.TaskResultData();
|
|
mask.tenant = ctx.tenant;
|
|
mask.key = taskWithCallback.key;
|
|
mask.status = commonDefines.FileStatus.None;
|
|
mask.callback = 'NOT_EMPTY';
|
|
|
|
// Update task: change status to SaveVersion
|
|
const updateTask = new taskResult.TaskResultData();
|
|
updateTask.status = commonDefines.FileStatus.SaveVersion;
|
|
updateTask.statusInfo = constants.NO_ERROR;
|
|
|
|
const result = await taskResult.updateIf(ctx, updateTask, mask);
|
|
|
|
// Should update exactly 1 row (the one with callback)
|
|
expect(result.affectedRows).toEqual(1);
|
|
|
|
// Verify the row with callback was updated
|
|
const updatedRow = await executeSql(`SELECT status FROM ${cfgTableResult} WHERE id = '${taskWithCallback.key}';`);
|
|
expect(updatedRow[0].status).toEqual(commonDefines.FileStatus.SaveVersion);
|
|
|
|
// Verify the row without callback was NOT updated
|
|
const notUpdatedRow = await executeSql(`SELECT status FROM ${cfgTableResult} WHERE id = '${taskEmptyCallback.key}';`);
|
|
expect(notUpdatedRow[0].status).toEqual(commonDefines.FileStatus.None);
|
|
});
|
|
});
|
|
});
|