diff --git a/CHANGELOG.md b/CHANGELOG.md index d78b2ba..9f3de83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,10 @@ -## xx.xx.xx +## 24.10.0 - Default max segmentation value count changed from 30 to 100 -- Mitigated an issue where attempting to read a missing or empty file could result in the creation of an unintended empty file. -- Added a new init time config option (conf.storage_type) which can make user set among these storage options: +- Mitigated an issue where SDK could create an unintended dump file +- Added a new init time config option (conf.storage_type) which can make user set the SDK storage option: - File Storage - Memory Only Storage +- Added a new init time config option (conf.custom_storage_method) which enables user to provide custom storage methods ## 22.06.0 - Fixed a bug where remote config requests were rejected diff --git a/lib/countly-bulk.js b/lib/countly-bulk.js index 1fe7235..a7ecc0e 100644 --- a/lib/countly-bulk.js +++ b/lib/countly-bulk.js @@ -25,7 +25,7 @@ var cc = require("./countly-common"); var BulkUser = require("./countly-bulk-user"); var CountlyStorage = require("./countly-storage"); -const StorageTypes = cc.storageTypeEnums; +CountlyBulk.StorageTypes = cc.storageTypeEnums; /** * @lends module:lib/countly-bulk @@ -39,7 +39,6 @@ const StorageTypes = cc.storageTypeEnums; * @param {number} [conf.fail_timeout=60] - set time in seconds to wait after failed connection to server in seconds * @param {number} [conf.session_update=60] - how often in seconds should session be extended * @param {number} [conf.max_events=100] - maximum amount of events to send in one batch - * @param {boolean} [conf.persist_queue=false] - persistently store queue until processed, default is false if you want to keep queue in memory and process all in one process run * @param {boolean} [conf.force_post=false] - force using post method for all requests * @param {string} [conf.storage_path] - where SDK would store data, including id, queues, etc * @param {string} [conf.http_options=] - function to get http options by reference and overwrite them, before running each request @@ -50,6 +49,8 @@ const StorageTypes = cc.storageTypeEnums; * @param {number} [conf.max_stack_trace_lines_per_thread=30] - maximum amount of stack trace lines would be recorded per thread * @param {number} [conf.max_stack_trace_line_length=200] - maximum amount of characters are allowed per stack trace line. This limits also the crash message length * @param {StorageTypes} conf.storage_type - to determine which storage type is going to be applied + * @param {Object} conf.custom_storage_method - user given storage methods + * @param {boolean} [conf.persist_queue=false] - *DEPRECATED* persistent mode instead of using in-memory queue. Use storage_type and storage_path instead * @example * var server = new CountlyBulk({ * app_key: "{YOUR-API-KEY}", @@ -75,8 +76,6 @@ function CountlyBulk(conf) { var maxBreadcrumbCount = 100; var maxStackTraceLinesPerThread = 30; var maxStackTraceLineLength = 200; - var storageType = StorageTypes.FILE; - cc.debugBulk = conf.debug || false; if (!conf.app_key) { cc.log(cc.logLevelEnums.ERROR, "CountlyBulk, 'app_key' is missing."); @@ -105,9 +104,13 @@ function CountlyBulk(conf) { conf.maxBreadcrumbCount = conf.max_breadcrumb_count || maxBreadcrumbCount; conf.maxStackTraceLinesPerThread = conf.max_stack_trace_lines_per_thread || maxStackTraceLinesPerThread; conf.maxStackTraceLineLength = conf.max_stack_trace_line_length || maxStackTraceLineLength; - conf.storage_type = conf.storage_type || storageType; - CountlyStorage.initStorage(conf.storage_path, conf.storage_type, true, conf.persist_queue); + // bulk mode is memory only by default + if (typeof conf.storage_type === "undefined" && conf.persist_queue === false) { + conf.storage_type = CountlyBulk.StorageTypes.MEMORY; + } + + CountlyStorage.initStorage(conf.storage_path, conf.storage_type, true, conf.custom_storage_method); this.conf = conf; /** @@ -599,6 +602,27 @@ function CountlyBulk(conf) { var requestQueue = CountlyStorage.storeGet("cly_req_queue", []); var eventQueue = CountlyStorage.storeGet("cly_bulk_event", {}); var bulkQueue = CountlyStorage.storeGet("cly_bulk_queue", []); + /** + * getBulkEventQueue is a testing purposed method which returns the event queue object + * @returns {Object} eventQueue + */ + this._getBulkEventQueue = function() { + return eventQueue; + }; + /** + * getBulkRequestQueue is a testing purposed method which returns the request queue object + * @returns {Object} requestQueue + */ + this._getBulkRequestQueue = function() { + return requestQueue; + }; + /** + * getBulkQueue is a testing purposed method which returns the bulk queue object + * @returns {Object} bulkQueue + */ + this._getBulkQueue = function() { + return bulkQueue; + }; } module.exports = CountlyBulk; diff --git a/lib/countly-common.js b/lib/countly-common.js index 7a961e5..c721bb1 100644 --- a/lib/countly-common.js +++ b/lib/countly-common.js @@ -54,7 +54,6 @@ var cc = { storageTypeEnums: { FILE: "file", MEMORY: "memory", - CUSTOM: "custom", }, /** diff --git a/lib/countly-storage.js b/lib/countly-storage.js index 27e1a04..3aeb309 100644 --- a/lib/countly-storage.js +++ b/lib/countly-storage.js @@ -2,14 +2,50 @@ const fs = require('fs'); const path = require('path'); var cc = require("./countly-common"); +// Constants +const defaultPath = "../data/"; // Default storage path +const defaultBulkPath = "../bulk_data/"; // Default bulk storage path +const StorageTypes = cc.storageTypeEnums; +const defaultStorageType = StorageTypes.FILE; +const customTypeName = "custom"; + var storagePath; -var __data = {}; -var defaultPath = "../data/"; // Default path -var defaultBulkPath = "../bulk_data/"; // Default path +let storageMethod = {}; +var __cache = {}; var asyncWriteLock = false; var asyncWriteQueue = []; -let storageMethod = {}; -const StorageTypes = cc.storageTypeEnums; + +/** + * Sets the storage method, by default sets file storage and storage path. + * @param {String} userPath - User provided storage path + * @param {StorageTypes} storageType - Whether to use memory only storage or not + * @param {Boolean} isBulk - Whether the storage is for bulk data + * @param {varies} customStorageMethod - Storage methods provided by the user + */ +var initStorage = function(userPath, storageType, isBulk = false, customStorageMethod = null) { + cc.log(cc.logLevelEnums.INFO, `Initializing storage with userPath: [${userPath}], storageType: [${storageType}], isBulk: [${isBulk}], customStorageMethod type: [${typeof customStorageMethod}].`); + + // set storage type + storageType = storageType || defaultStorageType; + storageMethod = fileStorage; // file storage is default + + if (storageType === StorageTypes.MEMORY) { + storageMethod = memoryStorage; + cc.log(cc.logLevelEnums.DEBUG, `Using memory storage!`); + } + + // at this point we either use memory or file storage. If custom storage is provided, check if it is valid and use it instead + if (isCustomStorageValid(customStorageMethod)) { + storageMethod = customStorageMethod; + storageType = customTypeName; + cc.log(cc.logLevelEnums.DEBUG, `Using custom storage!`); + } + + // set storage path if not memory storage + if (storageType !== StorageTypes.MEMORY) { + setStoragePath(userPath, isBulk); + } +}; // Memory-only storage methods const memoryStorage = { @@ -22,7 +58,7 @@ const memoryStorage = { storeSet: function(key, value, callback) { if (key) { cc.log(cc.logLevelEnums.DEBUG, `storeSet, Setting key: [${key}] & value: [${value}]!`); - __data[key] = value; + __cache[key] = value; if (typeof callback === "function") { callback(null); } @@ -39,14 +75,14 @@ const memoryStorage = { */ storeGet: function(key, def) { cc.log(cc.logLevelEnums.DEBUG, `storeGet, Fetching item from memory with key: [${key}].`); - return typeof __data[key] !== "undefined" ? __data[key] : def; + return typeof __cache[key] !== "undefined" ? __cache[key] : def; }, /** * Remove value from memory * @param {String} key - key of value to remove */ storeRemove: function(key) { - delete __data[key]; + delete __cache[key]; }, }; @@ -59,7 +95,7 @@ const fileStorage = { * @param {Function} callback - callback to call when done storing */ storeSet: function(key, value, callback) { - __data[key] = value; + __cache[key] = value; if (!asyncWriteLock) { asyncWriteLock = true; writeFile(key, value, callback); @@ -76,7 +112,7 @@ const fileStorage = { */ storeGet: function(key, def) { cc.log(cc.logLevelEnums.DEBUG, `storeGet, Fetching item from storage with key: [${key}].`); - if (typeof __data[key] === "undefined") { + if (typeof __cache[key] === "undefined") { var ob = readFile(key); var obLen; // check if the 'read object' is empty or not @@ -90,17 +126,17 @@ const fileStorage = { // if empty or falsy set default value if (!ob || obLen === 0) { - __data[key] = def; + __cache[key] = def; } // else set the value read file has else { - __data[key] = ob[key]; + __cache[key] = ob[key]; } } - return __data[key]; + return __cache[key]; }, storeRemove: function(key) { - delete __data[key]; + delete __cache[key]; var filePath = path.resolve(__dirname, `${getStoragePath()}__${key}.json`); fs.access(filePath, fs.constants.F_OK, (accessErr) => { if (accessErr) { @@ -119,35 +155,31 @@ const fileStorage = { }, }; -/** - * Sets the storage method, by default sets file storage and storage path. - * @param {String} userPath - User provided storage path - * @param {StorageTypes} storageType - Whether to use memory only storage or not - * @param {Boolean} isBulk - Whether the storage is for bulk data - * @param {Boolean} persistQueue - Whether to persist the queue until processed - */ -var initStorage = function(userPath, storageType, isBulk = false, persistQueue = false) { - if (storageType === StorageTypes.MEMORY) { - storageMethod = memoryStorage; +var isCustomStorageValid = function(storage) { + if (!storage) { + return false; + } + if (typeof storage.storeSet !== 'function') { + return false; + } + if (typeof storage.storeGet !== 'function') { + return false; } - else { - storageMethod = fileStorage; - setStoragePath(userPath, isBulk, persistQueue); + if (typeof storage.storeRemove !== 'function') { + return false; } + return true; }; /** * Sets the storage path, defaulting to a specified path if none is provided. * @param {String} userPath - User provided storage path * @param {Boolean} isBulk - Whether the storage is for bulk data - * @param {Boolean} persistQueue - Whether to persist the queue until processed */ -var setStoragePath = function(userPath, isBulk = false, persistQueue = false) { +var setStoragePath = function(userPath, isBulk = false) { storagePath = userPath || (isBulk ? defaultBulkPath : defaultPath); - if (!isBulk || persistQueue) { - createDirectory(path.resolve(__dirname, storagePath)); - } + createDirectory(path.resolve(__dirname, storagePath)); }; /** @@ -178,9 +210,10 @@ var createDirectory = function(dir) { */ var resetStorage = function() { storagePath = undefined; - __data = {}; + __cache = {}; asyncWriteLock = false; asyncWriteQueue = []; + storageMethod = {}; }; /** @@ -226,10 +259,10 @@ var readFile = function(key) { * Force store data synchronously on unrecoverable errors to preserve it for next launch */ var forceStore = function() { - for (var i in __data) { + for (var i in __cache) { var dir = path.resolve(__dirname, `${getStoragePath()}__${i}.json`); var ob = {}; - ob[i] = __data[i]; + ob[i] = __cache[i]; try { fs.writeFileSync(dir, JSON.stringify(ob)); } @@ -292,7 +325,12 @@ var getStorageType = function() { if (storageMethod === memoryStorage) { return StorageTypes.MEMORY; } - return StorageTypes.FILE; + + if (storageMethod === fileStorage) { + return StorageTypes.FILE; + } + + return null; }; module.exports = { diff --git a/lib/countly.js b/lib/countly.js index e68fd9e..761ca7a 100644 --- a/lib/countly.js +++ b/lib/countly.js @@ -29,8 +29,7 @@ var Bulk = require("./countly-bulk"); var CountlyStorage = require("./countly-storage"); var Countly = {}; -const StorageTypes = cc.storageTypeEnums; - +Countly.StorageTypes = cc.storageTypeEnums; Countly.Bulk = Bulk; (function() { var SDK_VERSION = "24.10.0"; @@ -72,7 +71,7 @@ Countly.Bulk = Bulk; var maxStackTraceLinesPerThread = 30; var maxStackTraceLineLength = 200; var deviceIdType = null; - var storageType = StorageTypes.FILE; + var heartBeatTimer = null; /** * Array with list of available features that you can require consent for */ @@ -124,6 +123,7 @@ Countly.Bulk = Bulk; * @param {string} conf.metrics._locale - locale or language of the device in ISO format * @param {string} conf.metrics._store - source from where the user/device/installation came from * @param {StorageTypes} conf.storage_type - to determine which storage type is going to be applied + * @param {Object} conf.custom_storage_method - user given storage methods * @example * Countly.init({ * app_key: "{YOUR-APP-KEY}", @@ -168,11 +168,12 @@ Countly.Bulk = Bulk; Countly.maxBreadcrumbCount = conf.max_breadcrumb_count || Countly.max_breadcrumb_count || conf.max_logs || Countly.max_logs || maxBreadcrumbCount; Countly.maxStackTraceLinesPerThread = conf.max_stack_trace_lines_per_thread || Countly.max_stack_trace_lines_per_thread || maxStackTraceLinesPerThread; Countly.maxStackTraceLineLength = conf.max_stack_trace_line_length || Countly.max_stack_trace_line_length || maxStackTraceLineLength; - conf.storage_type = conf.storage_type || storageType; + conf.storage_path = conf.storage_path || Countly.storage_path; + conf.storage_type = conf.storage_type || Countly.storage_type; // Common module debug value is set to init time debug value cc.debug = conf.debug; - CountlyStorage.initStorage(conf.storage_path, conf.storage_type); + CountlyStorage.initStorage(conf.storage_path, conf.storage_type, false, conf.custom_storage_method); // clear stored device ID if flag is set if (conf.clear_stored_device_id) { @@ -320,6 +321,10 @@ Countly.Bulk = Bulk; maxStackTraceLinesPerThread = 30; maxStackTraceLineLength = 200; deviceIdType = null; + if (heartBeatTimer) { + clearInterval(heartBeatTimer); + heartBeatTimer = null; + } // cc DEBUG cc.debug = false; @@ -334,6 +339,7 @@ Countly.Bulk = Bulk; // device_id Countly.device_id = undefined; + Countly.device_id_type = undefined; Countly.remote_config = undefined; Countly.require_consent = false; Countly.debug = undefined; @@ -1478,7 +1484,7 @@ Countly.Bulk = Bulk; }, "heartBeat", false); } - setTimeout(heartBeat, beatInterval); + heartBeatTimer = setTimeout(heartBeat, beatInterval); } /** diff --git a/test/helpers/helper_functions.js b/test/helpers/helper_functions.js index 1728687..d8340f6 100644 --- a/test/helpers/helper_functions.js +++ b/test/helpers/helper_functions.js @@ -1,25 +1,46 @@ /* eslint-disable no-unused-vars */ +/* eslint-disable no-console */ +/* global runthis */ var path = require("path"); var assert = require("assert"); var fs = require("fs"); +var fsp = require("fs/promises"); var Countly = require("../../lib/countly"); var CountlyStorage = require("../../lib/countly-storage"); // paths for convenience var dir = path.resolve(__dirname, "../../"); -var idDir = (`${dir}/data/__cly_id.json`); -var idTypeDir = (`${dir}/data/__cly_id_type.json`); -var eventDir = (`${dir}/data/__cly_event.json`); -var reqDir = (`${dir}/data/__cly_queue.json`); -var bulkEventDir = (`${dir}/bulk_data/__cly_bulk_event.json`); -var bulkQueueDir = (`${dir}/bulk_data/__cly_req_queue.json`); +var dir_test = path.resolve(__dirname, "../"); + +// paths for convenience +const DIR_CLY = (`${dir}/data`); +const DIR_CLY_ID = (`${dir}/data/__cly_id.json`); +const DIR_CLY_ID_type = (`${dir}/data/__cly_id_type.json`); +const DIR_CLY_event = (`${dir}/data/__cly_event.json`); +const DIR_CLY_request = (`${dir}/data/__cly_queue.json`); + +// Bulk paths for convenience +const DIR_Bulk = (`${dir}/bulk_data`); +const DIR_Bulk_bulk = (`${dir}/bulk_data/__cly_bulk_queue.json`); +const DIR_Bulk_event = (`${dir}/bulk_data/__cly_bulk_event.json`); +const DIR_Bulk_request = (`${dir}/bulk_data/__cly_req_queue.json`); + +// Custom +const DIR_Test = (`${dir_test}/customStorageDirectory`); +const DIR_Test_event = (`${dir_test}/customStorageDirectory/__cly_event.json`); +const DIR_Test_request = (`${dir_test}/customStorageDirectory/__cly_queue.json`); +const DIR_Test_bulk = (`${dir_test}/customStorageDirectory/__cly_bulk_queue.json`); +const DIR_Test_bulk_event = (`${dir_test}/customStorageDirectory/__cly_bulk_event.json`); +const DIR_Test_bulk_request = (`${dir_test}/customStorageDirectory/__cly_req_queue.json`); + // timeout variables -var sWait = 50; -var mWait = 3000; -var lWait = 10000; +const sWait = 50; +const mWait = 3000; +const lWait = 10000; + // parsing event queue function readEventQueue(givenPath = null, isBulk = false) { - var destination = eventDir; + var destination = DIR_CLY_event; if (givenPath !== null) { destination = givenPath; } @@ -30,50 +51,69 @@ function readEventQueue(givenPath = null, isBulk = false) { return a; } // parsing request queue -function readRequestQueue(givenPath = null, isBulk = false) { - var destination = reqDir; - if (givenPath !== null) { - destination = givenPath; +function readRequestQueue(customPath = false, isBulk = false, isMemory = false) { + var destination = DIR_CLY_request; + if (customPath) { + destination = DIR_Test_request; } - var a = JSON.parse(fs.readFileSync(destination, "utf-8")).cly_queue; + var a; if (isBulk) { a = JSON.parse(fs.readFileSync(destination, "utf-8")).cly_req_queue; } + if (isMemory) { + a = CountlyStorage.storeGet("cly_queue"); + } + else { + a = JSON.parse(fs.readFileSync(destination, "utf-8")).cly_queue; + } return a; } -function doesFileStoragePathsExist(callback) { - fs.access(idDir, fs.constants.F_OK, (err1) => { - fs.access(idTypeDir, fs.constants.F_OK, (err2) => { - fs.access(eventDir, fs.constants.F_OK, (err3) => { - // If all err variables are null, all files exist - const allFilesExist = !err1 && !err2 && !err3; - callback(allFilesExist); - }); +function doesFileStoragePathsExist(callback, isBulk = false, testPath = false) { + var paths = [DIR_CLY_ID, DIR_CLY_ID_type, DIR_CLY_event, DIR_CLY_request]; + + if (isBulk) { + paths = [DIR_Bulk_request, DIR_Bulk_event, DIR_Bulk_bulk]; + } + else if (testPath) { + paths = [DIR_Test_event, DIR_Test_request]; + } + + let errors = 0; + paths.forEach((p, index) => { + fs.access(p, fs.constants.F_OK, (err) => { + if (err) { + errors++; + } + if (index === p.length - 1) { + callback(errors === 0); + } }); }); } -function clearStorage(keepID = false, isBulk = false, customDir = '') { - // Resets Countly +async function clearStorage(customPath = null) { Countly.halt(true); - // Determine the directory based on isBulk or customDir - const eventDirectory = customDir || (isBulk ? bulkEventDir : eventDir); - const reqDirectory = customDir || (isBulk ? bulkQueueDir : reqDir); - // Helper function to remove directory and files - function removeDir(directory) { - if (fs.existsSync(directory)) { - fs.rmSync(directory, { recursive: true, force: true }); - } + + const relativePath = `../${customPath}`; + const resolvedCustomPath = path.resolve(__dirname, relativePath); + + await fsp.rm(DIR_CLY, { recursive: true, force: true }).catch(() => { }); + await fsp.rm(DIR_Bulk, { recursive: true, force: true }).catch(() => { }); + await fsp.rm(DIR_Test, { recursive: true, force: true }).catch(() => { }); + + if (resolvedCustomPath !== null && typeof resolvedCustomPath === 'string') { + await fsp.rm(resolvedCustomPath, { recursive: true, force: true }).catch(() => { }); } - // Remove event directory if it exists - removeDir(eventDirectory); - // Remove request directory if it exists - removeDir(reqDirectory); - // Optionally keep the ID directory - if (!keepID) { - removeDir(idDir); - removeDir(idTypeDir); + + const storageExists = await fsp.access(DIR_CLY).then(() => true).catch(() => false); + const bulkStorageExists = await fsp.access(DIR_Bulk).then(() => true).catch(() => false); + const customTestStorage = await fsp.access(DIR_Test).then(() => true).catch(() => false); + const customStorageExists = resolvedCustomPath !== null ? await fsp.access(resolvedCustomPath).then(() => true).catch(() => false) : false; + + if (storageExists || bulkStorageExists || customTestStorage || customStorageExists) { + throw new Error("Failed to clear storage"); } } + /** * bunch of tests specifically gathered for testing events * @param {Object} eventObject - Original event object to test @@ -132,7 +172,7 @@ function requestBaseParamValidator(resultingObject, id) { assert.ok(typeof resultingObject.sdk_version !== 'undefined'); assert.ok(typeof resultingObject.timestamp !== 'undefined'); assert.ok(resultingObject.dow > -1 && resultingObject.dow < 24); - assert.ok(resultingObject.dow > 0 && resultingObject.dow < 8); + assert.ok(resultingObject.dow >= 0 && resultingObject.dow < 8); } /** * bunch of tests specifically gathered for testing crashes @@ -174,28 +214,41 @@ function sessionRequestValidator(beginSs, endSs, time, id) { assert.equal(time, endSs.session_duration); } } -/** - * bunch of tests specifically gathered for testing user details - * @param {Object} originalDetails - Original object that contains user details - * @param {Object} details - Object from cly_queue that corresponds to user details recording - */ -function userDetailRequestValidator(originalDetails, details) { - requestBaseParamValidator(details); - var user = JSON.parse(details.user_details); - assert.equal(originalDetails.name, user.name); - assert.equal(originalDetails.username, user.username); - assert.equal(originalDetails.email, user.email); - assert.equal(originalDetails.organization, user.organization); - assert.equal(originalDetails.phone, user.phone); - assert.equal(originalDetails.picture, user.picture); - assert.equal(originalDetails.gender, user.gender); - assert.equal(originalDetails.byear, user.byear); - if (typeof originalDetails.custom !== 'undefined') { - for (var key in originalDetails.custom) { - assert.equal(originalDetails.custom[key], user.custom[key]); + +function validateUserDetails(actual, expected) { + // Helper function to remove undefined values + const cleanObj = (obj) => { + if (typeof obj === "string") { + try { + // Parse if it's a JSON string + obj = JSON.parse(obj); + } + catch (e) { + console.error("Invalid JSON string:", obj); + // Return null for invalid JSON + return null; + } } + // Remove properties with undefined values + return Object.fromEntries(Object.entries(obj).filter(([_, value]) => value !== undefined)); + }; + const cleanedActual = cleanObj(actual); + const cleanedExpected = cleanObj(expected); + if (!cleanedActual || !cleanedExpected) { + // If either cleaned object is null, validation fails + return false; + } + // Perform deep strict comparison after cleaning up undefined values + try { + assert.deepStrictEqual(cleanedActual, cleanedExpected); + return true; + } + catch (e) { + console.log("Validation failed:", e); + return false; } } + /** * bunch of tests specifically gathered for testing page views * @param {Object} name - page name @@ -228,7 +281,7 @@ module.exports = { eventValidator, crashRequestValidator, sessionRequestValidator, - userDetailRequestValidator, + validateUserDetails, viewEventValidator, doesFileStoragePathsExist, }; \ No newline at end of file diff --git a/test/helpers/test_utils.js b/test/helpers/test_utils.js new file mode 100644 index 0000000..ded2f53 --- /dev/null +++ b/test/helpers/test_utils.js @@ -0,0 +1,98 @@ +var eventObj = { + key: "event_check", + count: 55, + sum: 3.14, + dur: 2000, + segmentation: { + string_value: "example", + number_value: 42, + boolean_value: true, + array_value: ["item1", "item2"], + object_value: { nested_key: "nested_value" }, + null_value: null, + undefined_value: undefined, + }, +}; + +var timedEventObj = { + key: "timed", + count: 1, + segmentation: { + app_version: "1.0", + country: "Turkey", + }, +}; + +var userDetailObj = { + name: "Alexandrina Jovovich", + username: "alex_jov", + email: "alex.jov@example.com", + organization: "TechNova", + phone: "+987654321", + picture: "https://example.com/images/profile_alex.jpg", + gender: "Female", + byear: 1992, + custom: { + string_value: "example", + number_value: 42, + boolean_value: true, + array_value: ["item1", "item2"], + object_value: { nested_key: "nested_value" }, + }, +}; + +const invalidStorageMethods = { + _storage: {}, + setInvalid: function() { + }, + getInvalid: function() { + }, + removeInvalid: function() { + }, +}; + +const customStorage = { + _storage: {}, + storeSet: function(key, value, callback) { + if (key) { + this._storage[key] = value; + if (typeof callback === "function") { + callback(null); + } + } + }, + storeGet: function(key, def) { + return typeof this._storage[key] !== "undefined" ? this._storage[key] : def; + }, + storeRemove: function(key) { + delete this._storage[key]; + }, +}; + +function getInvalidStorage() { + return invalidStorageMethods; +} + +function getCustomStorage() { + return customStorage; +} + +var getUserDetailsObj = function() { + return userDetailObj; +}; + +var getEventObj = function() { + return eventObj; +}; + +var getTimedEventObj = function() { + return timedEventObj; +}; + +module.exports = { + getEventObj, + getUserDetailsObj, + getTimedEventObj, + getInvalidStorage, + getCustomStorage, +}; \ No newline at end of file diff --git a/test/tests_bulk.js b/test/tests_bulk.js index 90b8b91..d0a6724 100644 --- a/test/tests_bulk.js +++ b/test/tests_bulk.js @@ -1,24 +1,15 @@ /* eslint-disable no-console */ /* global runthis */ -var path = require("path"); const assert = require("assert"); const CountlyBulk = require("../lib/countly-bulk"); var hp = require("./helpers/helper_functions"); var storage = require("../lib/countly-storage"); +var testUtils = require("./helpers/test_utils"); -// default paths -var dir = path.resolve(__dirname, "../"); -var bulkEventDir = (`${dir}/bulk_data/__cly_bulk_event.json`); -var bulkQueueDir = (`${dir}/bulk_data/__cly_req_queue.json`); +const { StorageTypes } = CountlyBulk; -function createBulk(storagePath) { - var bulk = new CountlyBulk({ - app_key: "YOUR_APP_KEY", - url: "https://try.count.ly", - storage_path: storagePath, - }); - return bulk; -} +var appKey = "YOUR_APP_KEY"; +var serverUrl = "https://tests.url.cly"; function validateCrash(validator, nonfatal) { assert.ok(validator.crash._os); @@ -32,183 +23,343 @@ function validateCrash(validator, nonfatal) { assert.equal(true, validator.crash._not_os_specific); } -// note: this can replace the current one in the helper functions -function validateUserDetails(actual, expected) { - const keys = ['name', 'username', 'email', 'organization', 'phone', 'picture', 'gender', 'byear', 'custom']; - let isValid = true; - - keys.forEach((key) => { - if (typeof actual[key] === 'object' && actual[key] !== null) { - if (Array.isArray(actual[key])) { - if (!Array.isArray(expected[key]) || JSON.stringify(actual[key]) !== JSON.stringify(expected[key])) { - console.error(`Mismatch for key "${key}": expected "${JSON.stringify(expected[key])}", but got "${JSON.stringify(actual[key])}"`); - isValid = false; - } - } - else { - if (JSON.stringify(actual[key]) !== JSON.stringify(expected[key])) { - console.error(`Mismatch for key "${key}": expected "${JSON.stringify(expected[key])}", but got "${JSON.stringify(actual[key])}"`); - isValid = false; - } - } - } - else if (actual[key] !== expected[key]) { - console.error(`Mismatch for key "${key}": expected "${expected[key]}", but got "${actual[key]}"`); - isValid = false; - } - }); - // Validate nested custom object separately - if (expected.custom && actual.custom) { - const customKeys = Object.keys(expected.custom); - customKeys.forEach((key) => { - if (typeof actual.custom[key] === 'object' && actual.custom[key] !== null) { - if (Array.isArray(actual.custom[key])) { - if (!Array.isArray(expected.custom[key]) || JSON.stringify(actual.custom[key]) !== JSON.stringify(expected.custom[key])) { - console.error(`Mismatch in custom object for key "${key}": expected "${JSON.stringify(expected.custom[key])}", but got "${JSON.stringify(actual.custom[key])}"`); - isValid = false; - } - } - else { - if (JSON.stringify(actual.custom[key]) !== JSON.stringify(expected.custom[key])) { - console.error(`Mismatch in custom object for key "${key}": expected "${JSON.stringify(expected.custom[key])}", but got "${JSON.stringify(actual.custom[key])}"`); - isValid = false; - } - } - } - else if (actual.custom[key] !== expected.custom[key]) { - console.error(`Mismatch in custom object for key "${key}": expected "${expected.custom[key]}", but got "${actual.custom[key]}"`); - isValid = false; - } - }); +// Create bulk data +function createBulkData(bulk) { + // Add an event + var user = bulk.add_user({ device_id: "testUser1" }); + user.add_event(testUtils.getEventObj()); + + // add user details + var user2 = bulk.add_user({ device_id: "testUser2" }); + user2.user_details(testUtils.getUserDetailsObj()); + + // add request + bulk.add_request({ device_id: "TestUser3" }); + + // add Crash + var user4 = bulk.add_user({ device_id: "TestUser4" }); + try { + runthis(); + } + catch (ex) { + user4.report_crash({ + _os: "Android", + _os_version: "7", + _error: "Stack trace goes here", + _app_version: "1.0", + _run: 12345, + _custom: {}, + _nonfatal: true, + _javascript: true, + _not_os_specific: true, + }, 1500645200); } - return isValid; } -var eventObj = { - key: "bulk_check", - count: 55, - sum: 3.14, - dur: 2000, - segmentation: { - string_value: "example", - number_value: 42, - boolean_value: true, - array_value: ["item1", "item2"], - object_value: { nested_key: "nested_value" }, - null_value: null, - undefined_value: undefined, - }, -}; - -var userDetailObj = { - name: "Alexandrina Jovovich", - username: "alex_jov", - email: "alex.jov@example.com", - organization: "TechNova", - phone: "+987654321", - picture: "https://example.com/images/profile_alex.jpg", - gender: "Female", - byear: 1992, // birth year - custom: { - string_value: "example", - number_value: 42, - boolean_value: true, - array_value: ["item1", "item2"], - object_value: { nested_key: "nested_value" }, - null_value: null, - undefined_value: undefined, - }, -}; +// Validate created bulk data +function validateCreatedBulkData(bulk) { + var events = bulk._getBulkEventQueue(); + var reqQueue = bulk._getBulkRequestQueue(); + var bulkQueue = bulk._getBulkQueue(); + + assert.equal(Object.keys(events).length, 1); + assert.equal(reqQueue.length, 3); + assert.equal(bulkQueue.length, 0); + + var deviceEvents = events.testUser1; // Access the events for the specific device + var recordedEvent = deviceEvents[0]; // Access the first event + hp.eventValidator(testUtils.getEventObj(), recordedEvent); + + var req = reqQueue[0]; // read user details queue + const actualUserDetails = req.user_details; // Extract the user_details from the actual request + const isValid = hp.validateUserDetails(actualUserDetails, testUtils.getUserDetailsObj()); + assert.equal(true, isValid); + + var testUser3Request = reqQueue.find((request) => request.device_id === "TestUser3"); + assert.ok(testUser3Request); + assert.strictEqual(testUser3Request.device_id, "TestUser3"); + assert.strictEqual(testUser3Request.app_key, "YOUR_APP_KEY"); + assert.strictEqual(testUser3Request.sdk_name, "javascript_native_nodejs_bulk"); + + var testUser4Request = reqQueue.find((request) => request.device_id === "TestUser4"); + validateCrash(testUser4Request, true); +} + +function shouldFilesExist(shouldExist, isCustomTest = false) { + hp.doesFileStoragePathsExist((exists) => { + assert.equal(shouldExist, exists); + }, true, isCustomTest); +} describe("Bulk Tests", () => { - it("1- Bulk with Default Storage Path", (done) => { - hp.clearStorage(false, true); - createBulk(); + beforeEach(async() => { + await hp.clearStorage(); + }); + // without any config option default bulk storage + // bulk mode is memory only by default + it("1- CNR", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + }); + assert.equal(storage.getStoragePath(), undefined); + shouldFilesExist(false); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(false); + done(); + }, hp.mWait); + }); + + // storage path and storage type provided in bulk + // type should be file and path should be correct + it("2- CNR_cPath_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_path: "../test/customStorageDirectory/", + storage_type: StorageTypes.FILE, + }); + shouldFilesExist(true, true); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(true, true); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + done(); + }, hp.sWait); + }); + + // file storage type in config + // path should become the default "../bulk_data/" + it("3- CNR_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + }); assert.equal(storage.getStoragePath(), "../bulk_data/"); - done(); + shouldFilesExist(true); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../bulk_data/"); + done(); + }, hp.mWait); + }); + + // memory storage type in config + // path should become undefined and storage files shouldn't exist + it("4- CNR_memory", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.MEMORY, + }); + assert.equal(storage.getStoragePath(), undefined); + shouldFilesExist(false); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(false); + assert.equal(storage.getStoragePath(), undefined); + done(); + }, hp.mWait); }); - it("2- Bulk with Custom Storage Path", (done) => { - hp.clearStorage(false, true); - createBulk("../test/customStorageDirectory/"); + // custom storage path and memory storage type provided in config + // path should become undefined, type should be memory storage + it("5- CNR_cPath_memory", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_path: "../test/customStorageDirectory/", + storage_type: StorageTypes.MEMORY, + }); + assert.equal(storage.getStoragePath(), undefined); + shouldFilesExist(false); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(false); + assert.equal(storage.getStoragePath(), undefined); + done(); + }, hp.mWait); + }); + + // persist_queue is true in bulk config + // should switch to file storage and default bulk path + it("6- CNR_persistTrue", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + persist_queue: true, + }); + assert.equal(storage.getStoragePath(), "../bulk_data/"); + shouldFilesExist(true); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../bulk_data/"); + done(); + }, hp.mWait); + }); + + // persist_queue is false in bulk config + // should result same with default bulk configurations + it("7- CNR_persistFalse", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + persist_queue: false, + }); + assert.equal(storage.getStoragePath(), undefined); + shouldFilesExist(false); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(false); + assert.equal(storage.getStoragePath(), undefined); + done(); + }, hp.mWait); + }); + + // custom path is provided and persist_queue is true in config + // custom path should become the storage path and switch to file storage + it("8- CNR_cPath_persistTrue", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_path: "../test/customStorageDirectory/", + persist_queue: true, + }); assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); - done(); + shouldFilesExist(true); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + done(); + }, hp.mWait); }); - it("3- Bulk add_user with Record Event", (done) => { - hp.clearStorage(false, true); - var bulk = createBulk(); - var user = bulk.add_user({ device_id: "testUser1" }); - user.add_event(eventObj); + // custom path is provided and persist_queue is false in config + // storage path should become undefined, should stay in memory storage + it("9- CNR_cPath_persistFalse", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_path: "../test/customStorageDirectory/", + persist_queue: false, + }); + assert.equal(storage.getStoragePath(), undefined); + shouldFilesExist(false); + createBulkData(bulk); + setTimeout(() => { - var events = hp.readEventQueue(bulkEventDir, true); - var deviceEvents = events.testUser1; // Access the events for the specific device - var recordedEvent = deviceEvents[0]; // Access the first event - hp.eventValidator(eventObj, recordedEvent); + validateCreatedBulkData(bulk); + shouldFilesExist(false); + assert.equal(storage.getStoragePath(), undefined); done(); }, hp.mWait); }); - it("4- Bulk add_user with User Details", (done) => { - hp.clearStorage(false, true); - var bulk = createBulk(); - var user = bulk.add_user({ device_id: "testUser2" }); - user.user_details(userDetailObj); + // persist_queue is true and storage type is file + // should directly switch to the default file storage for bulk + it("10- CNR_persistTrue_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + persist_queue: true, + }); + assert.equal(storage.getStoragePath(), "../bulk_data/"); + shouldFilesExist(true); + createBulkData(bulk); - // read event queue setTimeout(() => { - var reqQueue = hp.readRequestQueue(bulkQueueDir, true); - var req = reqQueue[0]; - // Extract the user_details from the actual request - const actualUserDetails = req.user_details || {}; - // Validate the user details - const isValid = validateUserDetails(actualUserDetails, userDetailObj); - assert.equal(true, isValid); + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../bulk_data/"); done(); - }, hp.sWait); + }, hp.mWait); }); - it("5- Bulk add_request", (done) => { - hp.clearStorage(false, true); - var bulk = createBulk(); - bulk.add_request({ device_id: "TestUser3" }); + // persist_queue is false and storage type is file + // storage type should overrule and switch into the default bulk file storage + it("11- CNR_persistFalse_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + persist_queue: true, + }); + assert.equal(storage.getStoragePath(), "../bulk_data/"); + shouldFilesExist(true); + createBulkData(bulk); + setTimeout(() => { - var reqQueue = hp.readRequestQueue(bulkQueueDir, true); - var testUser3Request = reqQueue.find((req) => req.device_id === "TestUser3"); - assert.ok(testUser3Request); - assert.strictEqual(testUser3Request.device_id, "TestUser3"); - assert.strictEqual(testUser3Request.app_key, "YOUR_APP_KEY"); - assert.strictEqual(testUser3Request.sdk_name, "javascript_native_nodejs_bulk"); + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../bulk_data/"); done(); - }, hp.sWait); + }, hp.mWait); }); - it("6- Bulk add_user Report Crash", (done) => { - hp.clearStorage(false, true); - var bulk = createBulk(); - var user = bulk.add_user({ device_id: "TestUser4" }); - try { - runthis(); - } - catch (ex) { - user.report_crash({ - _os: "Android", - _os_version: "7", - _error: "Stack trace goes here", - _app_version: "1.0", - _run: 12345, - _custom: {}, - _nonfatal: true, - _javascript: true, - _not_os_specific: true, - }, 1500645200); - } - // read event queue + // persist_queue is true and storage type is file and custom path is given + // storage type should overrule and switch into the custom path file storage + it("12- CNR_cPath_persistTrue_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + storage_path: "../test/customStorageDirectory/", + persist_queue: true, + }); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + shouldFilesExist(true); + createBulkData(bulk); + setTimeout(() => { - var reqQueue = hp.readRequestQueue(bulkQueueDir, true); - var testUser4Request = reqQueue.find((req) => req.device_id === "TestUser4"); - validateCrash(testUser4Request, true); + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); done(); - }, hp.sWait); + }, hp.mWait); + }); + + // persist_queue is false and storage type is file and custom path is given + // storage type should overrule and switch into the custom path file storage + it("13- CNR_cPath_persistFalse_file", (done) => { + var bulk = new CountlyBulk({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + storage_path: "../test/customStorageDirectory/", + persist_queue: false, + }); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + shouldFilesExist(true); + createBulkData(bulk); + + setTimeout(() => { + validateCreatedBulkData(bulk); + shouldFilesExist(true); + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + done(); + }, hp.mWait); }); }); \ No newline at end of file diff --git a/test/tests_consents.js b/test/tests_consents.js index 057f5d7..a89b4e1 100644 --- a/test/tests_consents.js +++ b/test/tests_consents.js @@ -62,8 +62,10 @@ function events() { // tests describe("Internal event consent tests", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("Only custom event should be sent to the queue", (done) => { - hp.clearStorage(); initMain(); Countly.add_consent(["events"]); events(); @@ -75,7 +77,6 @@ describe("Internal event consent tests", () => { }, hp.sWait); }); it("All but custom event should be sent to the queue", (done) => { - hp.clearStorage(); initMain(); Countly.add_consent(["sessions", "views", "users", "star-rating", "apm", "feedback"]); events(); @@ -91,7 +92,6 @@ describe("Internal event consent tests", () => { }, hp.mWait); }); it("Non-merge ID change should reset all consents", (done) => { - hp.clearStorage(); initMain(); Countly.add_consent(["sessions", "views", "users", "star-rating", "apm", "feedback"]); Countly.change_id("Richard Wagner II", false); @@ -102,7 +102,6 @@ describe("Internal event consent tests", () => { }, hp.sWait); }); it("Merge ID change should not reset consents", (done) => { - hp.clearStorage(); initMain(); Countly.add_consent(["sessions", "views", "users", "star-rating", "apm", "feedback"]); // Countly.change_id("Richard Wagner the second", true); diff --git a/test/tests_crashes.js b/test/tests_crashes.js index 2adedb5..12f2688 100644 --- a/test/tests_crashes.js +++ b/test/tests_crashes.js @@ -14,9 +14,10 @@ function initMain() { } describe("Crash tests", () => { + before(async() => { + await hp.clearStorage(); + }); it("Validate handled error logic", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // error logic @@ -34,23 +35,25 @@ describe("Crash tests", () => { done(); }, hp.sWait); }); - // This needs two steps, first creating an error and second checking the logs without erasing, otherwise error would halt the test - describe("Unhandled Error logic", () => { - it("Create unhandled rejection", () => { - // clear previous data - hp.clearStorage(); - // initialize SDK - initMain(); - // send emitter - Countly.track_errors(); - process.emit('unhandledRejection'); - }); - it("Validate unhandled rejection recording", (done) => { - setTimeout(() => { - var req = hp.readRequestQueue()[0]; - hp.crashRequestValidator(req, false); - done(); - }, hp.mWait); - }); +}); +// This needs two steps, first creating an error and second checking the logs without erasing, otherwise error would halt the test +describe("Unhandled Error logic", () => { + before(async() => { + await hp.clearStorage(); + }); + it("Create unhandled rejection", (done) => { + // initialize SDK + initMain(); + // send emitter + Countly.track_errors(); + process.emit('unhandledRejection'); + done(); + }); + it("Validate unhandled rejection recording", (done) => { + setTimeout(() => { + var req = hp.readRequestQueue()[0]; + hp.crashRequestValidator(req, false); + done(); + }, hp.mWait); }); }); diff --git a/test/tests_device_id_type.js b/test/tests_device_id_type.js index afa76f4..650f1b5 100644 --- a/test/tests_device_id_type.js +++ b/test/tests_device_id_type.js @@ -55,9 +55,10 @@ function checkRequestsForT(queue, expectedInternalType) { } describe("Device ID type tests", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("1.Generated device ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.begin_session(); @@ -71,8 +72,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("2.Developer supplied device ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain("ID"); Countly.begin_session(); @@ -86,8 +85,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("3.With stored dev ID and no new ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain("ID"); Countly.begin_session(); @@ -109,8 +106,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("4.With stored dev ID and with new ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain("ID"); Countly.begin_session(); @@ -132,8 +127,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("5.With stored generated ID and no new ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.begin_session(); @@ -157,8 +150,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("6.With stored generated ID and with new ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.begin_session(); @@ -182,8 +173,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("7.With stored dev ID and no new ID, flag set", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain("ID"); Countly.begin_session(); @@ -206,31 +195,29 @@ describe("Device ID type tests", () => { }); it("8.With stored dev ID and with new ID, flag set", (done) => { - // clear previous data - hp.clearStorage(); - // initialize SDK - initMain("ID"); - Countly.begin_session(); - // read request queue setTimeout(() => { - var rq = hp.readRequestQueue()[0]; - assert.equal(Countly.get_device_id(), "ID"); - assert.equal(Countly.get_device_id_type(), cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - checkRequestsForT(rq, cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - Countly.halt(true); - initMain("ID2", true); + // initialize SDK + initMain("ID"); + Countly.begin_session(); + // read request queue setTimeout(() => { - var req = hp.readRequestQueue()[0]; - assert.equal(Countly.get_device_id(), "ID2"); + var rq = hp.readRequestQueue()[0]; + assert.equal(Countly.get_device_id(), "ID"); assert.equal(Countly.get_device_id_type(), cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - checkRequestsForT(req, cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - done(); - }, hp.sWait); - }, hp.sWait); + checkRequestsForT(rq, cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); + Countly.halt(true); + initMain("ID2", true); + setTimeout(() => { + var req = hp.readRequestQueue()[0]; + assert.equal(Countly.get_device_id(), "ID2"); + assert.equal(Countly.get_device_id_type(), cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); + checkRequestsForT(req, cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); + done(); + }, hp.sWait); + }, hp.lWait); + }, hp.lWait); }); it("9.With stored sdk ID and no new ID, flag set", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.begin_session(); @@ -255,8 +242,6 @@ describe("Device ID type tests", () => { }); it("10.With stored sdk ID and with new ID, flag set", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.begin_session(); @@ -280,8 +265,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("11.Change generated device ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(undefined); Countly.change_id("changedID"); @@ -298,8 +281,6 @@ describe("Device ID type tests", () => { }, hp.sWait); }); it("12.Change developer supplied device ID", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain("ID"); Countly.change_id("changedID"); diff --git a/test/tests_events.js b/test/tests_events.js index 9e93305..127c4d5 100644 --- a/test/tests_events.js +++ b/test/tests_events.js @@ -1,6 +1,7 @@ /* eslint-disable no-console */ var Countly = require("../lib/countly"); var hp = require("./helpers/helper_functions"); +var testUtils = require("./helpers/test_utils"); // init function function initMain() { @@ -11,54 +12,34 @@ function initMain() { max_events: -1, }); } -// an event object to use -var eventObj = { - key: "in_app_purchase", - count: 3, - sum: 2.97, - dur: 1000, - segmentation: { - app_version: "1.0", - country: "Turkey", - }, -}; -// a timed event object -var timedEventObj = { - key: "timed", - count: 1, - segmentation: { - app_version: "1.0", - country: "Turkey", - }, -}; + describe("Events tests", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("Record and check custom event", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // send custom event - Countly.add_event(eventObj); + Countly.add_event(testUtils.getEventObj()); // read event queue setTimeout(() => { var event = hp.readEventQueue()[0]; - hp.eventValidator(eventObj, event); + hp.eventValidator(testUtils.getEventObj(), event); done(); }, hp.mWait); }); it("Record and check timed events", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // send timed event Countly.start_event("timed"); setTimeout(() => { - Countly.end_event(timedEventObj); + Countly.end_event(testUtils.getTimedEventObj()); // read event queue setTimeout(() => { var event = hp.readEventQueue()[0]; - hp.eventValidator(timedEventObj, event, (hp.mWait / 1000)); + hp.eventValidator(testUtils.getTimedEventObj(), event, (hp.mWait / 1000)); done(); }, hp.sWait); }, hp.mWait); diff --git a/test/tests_internal_limits.js b/test/tests_internal_limits.js index 712da5d..82f0a10 100644 --- a/test/tests_internal_limits.js +++ b/test/tests_internal_limits.js @@ -22,6 +22,9 @@ function initLimitsMain() { // Integration tests with countly initialized describe("Testing internal limits", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); describe("Testing truncation functions", () => { it("truncateSingleValue: Check if the string is truncated", () => { var newStr = cc.truncateSingleValue("123456789", 3, "test"); @@ -56,8 +59,6 @@ describe("Testing internal limits", () => { }); it("1. Check custom event truncation", (done) => { - // clear storage - hp.clearStorage(); // init Countly initLimitsMain(); // send event @@ -88,8 +89,6 @@ describe("Testing internal limits", () => { }); it("2. Check countly view event truncation", (done) => { - // clear storage - hp.clearStorage(); // init Countly initLimitsMain(); // page view @@ -110,8 +109,6 @@ describe("Testing internal limits", () => { }, hp.sWait); }); it("3. Check breadcrumbs and error truncation", (done) => { - // clear storage - hp.clearStorage(); // init Countly initLimitsMain(); // add log @@ -154,8 +151,6 @@ describe("Testing internal limits", () => { }, hp.sWait); }); it("4. Check user details truncation", (done) => { - // clear storage - hp.clearStorage(); // init Countly initLimitsMain(); // add user details @@ -209,8 +204,6 @@ describe("Testing internal limits", () => { }, hp.sWait); }); it("5. Check custom properties truncation", (done) => { - // clear storage - hp.clearStorage(); // init Countly initLimitsMain(); // add custom properties diff --git a/test/tests_sessions.js b/test/tests_sessions.js index 2893cc7..ac17f3b 100644 --- a/test/tests_sessions.js +++ b/test/tests_sessions.js @@ -11,9 +11,10 @@ function initMain() { }); } describe("Sessions tests", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("Start session and validate the request queue", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // send session calls @@ -25,8 +26,6 @@ describe("Sessions tests", () => { }, hp.sWait); }); it("Start and end session and validate the request queue", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // send session calls diff --git a/test/tests_storage.js b/test/tests_storage.js index bc7d5e1..2e3c85e 100644 --- a/test/tests_storage.js +++ b/test/tests_storage.js @@ -1,429 +1,425 @@ +/* eslint-disable no-console */ +/* global runthis */ const assert = require("assert"); var Countly = require("../lib/countly"); var storage = require("../lib/countly-storage"); -var cc = require("../lib/countly-common"); var hp = require("./helpers/helper_functions"); +var testUtils = require("./helpers/test_utils"); -const StorageTypes = cc.storageTypeEnums; - -// example event object to use -var eventObj = { - key: "storage_check", - count: 5, - sum: 3.14, - dur: 2000, - segmentation: { - app_version: "1.0", - country: "Zambia", - }, -}; - -var userDetailObj = { - name: "Akira Kurosawa", - username: "a_kurosawa", - email: "akira.kurosawa@filmlegacy.com", - organization: "Toho Studios", - phone: "+81312345678", - picture: "https://example.com/profile_images/akira_kurosawa.jpg", - gender: "Male", - byear: 1910, - custom: { - "known for": "Film Director", - "notable works": "Seven Samurai, Rashomon, Ran", - }, -}; - -// init function -function initMain(device_id) { - Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - interval: 10000, - max_events: -1, - device_id: device_id, - }); -} -// TODO: move these to helpers to reduce duplication -function validateSdkGeneratedId(providedDeviceId) { - assert.ok(providedDeviceId); - assert.equal(providedDeviceId.length, 36); - assert.ok(cc.isUUID(providedDeviceId)); -} -function checkRequestsForT(queue, expectedInternalType) { - for (var i = 0; i < queue.length; i++) { - assert.ok(queue[i].t); - assert.equal(queue[i].t, expectedInternalType); - } -} -function validateDeviceId(deviceId, deviceIdType, expectedDeviceId, expectedDeviceIdType) { - var rq = hp.readRequestQueue()[0]; - if (expectedDeviceIdType === cc.deviceIdTypeEnums.SDK_GENERATED) { - validateSdkGeneratedId(deviceId); // for SDK-generated IDs - } - else { - assert.equal(deviceId, expectedDeviceId); // for developer-supplied IDs - } - assert.equal(deviceIdType, expectedDeviceIdType); - checkRequestsForT(rq, expectedDeviceIdType); +var appKey = "YOUR_APP_KEY"; +var serverUrl = "https://your.server.ly"; + +const { StorageTypes } = Countly; + +function shouldFilesExist(shouldExist, isCustomTest = false) { + hp.doesFileStoragePathsExist((exists) => { + assert.equal(shouldExist, exists); + }, false, isCustomTest); } -function recordValuesToStorageAndValidate(userPath, memoryOnly = false, isBulk = false, persistQueue = false) { - // Set values - var deviceIdType = cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED; - storage.initStorage(userPath, memoryOnly, isBulk, persistQueue); - storage.storeSet("cly_id", "SpecialDeviceId"); - storage.storeSet("cly_id_type", deviceIdType); - - // Set values with different data types + +function validateStorageMethods() { storage.storeSet("cly_count", 42); storage.storeSet("cly_object", { key: "value" }); storage.storeSet("cly_null", null); // Retrieve and assert values - assert.equal(storage.storeGet("cly_id"), "SpecialDeviceId"); - assert.equal(storage.storeGet("cly_id_type"), deviceIdType); assert.equal(storage.storeGet("cly_count"), 42); assert.deepEqual(storage.storeGet("cly_object"), { key: "value" }); assert.equal(storage.storeGet("cly_null"), null); +} + +function validateStorageTypeAndPath(expectedStorageType, isCustomPath = false) { + if (expectedStorageType === StorageTypes.FILE) { + if (!isCustomPath) { + assert.equal(storage.getStoragePath(), "../data/"); + assert.equal(storage.getStorageType(), StorageTypes.FILE); + } + else { + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + assert.equal(storage.getStorageType(), StorageTypes.FILE); + } + } + else if (expectedStorageType === StorageTypes.MEMORY) { + assert.equal(storage.getStoragePath(), undefined); + assert.equal(storage.getStorageType(), StorageTypes.MEMORY); + } + // get storage type returns null in case of a custom method + else if (expectedStorageType === null) { + if (!isCustomPath) { + assert.equal(storage.getStoragePath(), "../data/"); + assert.equal(storage.getStorageType(), null); + } + else { + assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); + assert.equal(storage.getStorageType(), null); + } + } +} +function createData() { + // begin a session + Countly.begin_session(true); + // add an event + Countly.add_event(testUtils.getEventObj()); + // add user details + Countly.user_details(testUtils.getUserDetailsObj()); + // add crash + Countly.track_errors(); + try { + runthis(); + } + catch (ex) { + Countly.log_error(ex); + } +} +function validateData(isCustomPath = false, isMemoryOrCustom = false) { + var beg = hp.readRequestQueue(isCustomPath, false, isMemoryOrCustom)[0]; + hp.sessionRequestValidator(beg); + + var ud = hp.readRequestQueue(isCustomPath, false, isMemoryOrCustom)[1]; + const isValid = hp.validateUserDetails(ud.user_details, testUtils.getUserDetailsObj()); + assert.equal(isValid, true); + + var crash = hp.readRequestQueue(isCustomPath, false, isMemoryOrCustom)[2]; + hp.crashRequestValidator(crash, true); - // Remove specific items by overriding with null or empty array - storage.storeSet("cly_id", null); - storage.storeSet("cly_object", []); - assert.equal(storage.storeGet("cly_id"), null); - assert.deepEqual(storage.storeGet("cly_object"), []); - - // Reset storage and check if it's empty again - storage.resetStorage(); - assert.equal(storage.storeGet("cly_id"), undefined); - assert.equal(storage.storeGet("cly_id_type"), undefined); - assert.equal(storage.storeGet("cly_count"), undefined); - assert.equal(storage.storeGet("cly_object"), undefined); - assert.equal(storage.storeGet("cly_null"), undefined); + var ev = hp.readRequestQueue(isCustomPath, false, isMemoryOrCustom)[3]; + var eventsArray = JSON.parse(ev.events); + hp.eventValidator(testUtils.getEventObj(), eventsArray[0]); } +/* ++---------------------------------------------------+-------------------+ +| Configuration Option | Tested? (+/-) | ++---------------------------------------------------+-------------------+ +| 1. No Configuration Option Provided | + | ++---------------------------------------------------+-------------------+ +| 2. File Storage with Custom Path | + | ++---------------------------------------------------+-------------------+ +| 3. File Storage with Invalid Path | + | ++---------------------------------------------------+-------------------+ +| 4. File Storage while Custom Method Provided | + | ++---------------------------------------------------+-------------------+ +| 5. Memory Storage with No Path | + | ++---------------------------------------------------+-------------------+ +| 6. Memory Storage with Custom Path | + | ++---------------------------------------------------+-------------------+ +| 7. Memory Storage while Custom Method Provided | + | ++---------------------------------------------------+-------------------+ +| 8. Custom Storage Methods with No Path | + | ++---------------------------------------------------+-------------------+ +| 9. Custom Storage Methods with Custom Path | + | ++---------------------------------------------------+-------------------+ +| 10. Custom Storage with Invalid Path | + | ++---------------------------------------------------+-------------------+ +| 11. Custom Storage Methods with Invalid Methods | + | ++---------------------------------------------------+-------------------+ +| 12. Init Storage default no SDK init | + | ++---------------------------------------------------+-------------------+ +| 13. File Storage with null path no SDK init | + | ++---------------------------------------------------+-------------------+ +| 14. File Storage with custom path no SDK init | + | ++---------------------------------------------------+-------------------+ +| 15. Memory Storage without no SDK init | + | ++---------------------------------------------------+-------------------+ +| 16. Custom Storage with null path no SDK init | + | ++---------------------------------------------------+-------------------+ +| 17. Custom Storage with custom path no SDK init | + | ++---------------------------------------------------+-------------------+ +*/ describe("Storage Tests", () => { - it("1- Store Generated Device ID", (done) => { - // clear previous data - hp.clearStorage(); - // initialize SDK - initMain(); - Countly.begin_session(); - // read request queue - setTimeout(() => { - validateSdkGeneratedId(Countly.get_device_id()); - done(); - }, hp.sWait); + beforeEach(async() => { + await hp.clearStorage(); }); - it("1.1- Validate generated device id after process restart", (done) => { - initMain(); - validateDeviceId(Countly.get_device_id(), Countly.get_device_id_type(), undefined, cc.deviceIdTypeEnums.SDK_GENERATED); - done(); - }); + // if no config option provided sdk should init storage with default settings + // "../data/" as the storage path, FILE as the storage type + it("1- noConfigOption", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + }); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE); + createData(); - it("2.Developer supplied device ID", (done) => { - hp.clearStorage(); - initMain("ID"); - Countly.begin_session(); setTimeout(() => { - validateDeviceId(Countly.get_device_id(), Countly.get_device_id_type(), "ID", cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE); + validateData(); done(); - }, hp.sWait); + }, hp.mWait); }); - it("2.1- Validate generated device id after process restart", (done) => { - validateDeviceId(Countly.get_device_id(), Countly.get_device_id_type(), "ID", cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - done(); - }); + // if custom path is provided sdk should init storage with using that path + it("2- file_cPath", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + storage_path: "../test/customStorageDirectory/", + storage_type: StorageTypes.FILE, + }); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE, true); + createData(); - it("3- Record and validate all user details", (done) => { - hp.clearStorage(); - initMain(); - Countly.user_details(userDetailObj); setTimeout(() => { - var req = hp.readRequestQueue()[0]; - hp.userDetailRequestValidator(userDetailObj, req); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE, true); + validateData(true); done(); - }, hp.sWait); + }, hp.mWait); }); - it("3.1- Validate stored user detail", (done) => { - var req = hp.readRequestQueue()[0]; - hp.userDetailRequestValidator(userDetailObj, req); - done(); - }); + // if invalid path is provided such as null or undefined sdk should init storage with default path + // validateStorageTypeAndPath checks path as "../data/" if true is not passed as second param + it("3- file_invalid_path", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + storage_path: undefined, + storage_type: StorageTypes.FILE, + }); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE); + createData(); - it("4- Record event and validate storage", (done) => { - hp.clearStorage(); - initMain(); - Countly.add_event(eventObj); setTimeout(() => { - var storedEvents = hp.readEventQueue(); - assert.strictEqual(storedEvents.length, 1, "There should be exactly one event stored"); - - var event = storedEvents[0]; - hp.eventValidator(eventObj, event); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE); + validateData(); done(); }, hp.mWait); }); - it("4.1- Validate event persistence after process restart", (done) => { - // Initialize SDK - initMain(); - - // Read stored events without clearing storage - var storedEvents = hp.readEventQueue(); - assert.strictEqual(storedEvents.length, 1, "There should be exactly one event stored"); - - var event = storedEvents[0]; - hp.eventValidator(eventObj, event); - done(); - }); - - // if storage path is not provided it will be default "../data/" - it("5- Not provide storage path during init", (done) => { - hp.clearStorage(); - initMain(); - assert.equal(storage.getStoragePath(), "../data/"); - done(); - }); - - // if set to undefined it should be set to default path - it("6- Set storage path to undefined", (done) => { - hp.clearStorage(); + // since a custom method is provided sdk will switch to using that + // custom method will be applied, storage type will be null but sdk will create storage files anyway + it("4- file_cMethod", (done) => { Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - storage_path: undefined, + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.FILE, + custom_storage_method: testUtils.getCustomStorage(), }); - assert.equal(storage.getStoragePath(), "../data/"); - done(); + shouldFilesExist(true); + // storage type will be null since custom method is provided + validateStorageTypeAndPath(null); + createData(); + + setTimeout(() => { + shouldFilesExist(true); + validateStorageTypeAndPath(null); + validateData(false, true); + done(); + }, hp.mWait); }); - // if set to null it should be set to default path - it("7- Set storage path to null", (done) => { - hp.clearStorage(); + // storage type will become memory, and storage files will not exist + it("5- memory_noPath", (done) => { Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - storage_path: null, + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.MEMORY, }); - assert.equal(storage.getStoragePath(), "../data/"); - done(); + shouldFilesExist(false); + validateStorageTypeAndPath(StorageTypes.MEMORY); + createData(); + + setTimeout(() => { + shouldFilesExist(false); + validateStorageTypeAndPath(StorageTypes.MEMORY); + validateData(false, true); + done(); + }, hp.mWait); }); - // it should be set to the custom directory if provided - it("8- Set storage path to custom directory", (done) => { - hp.clearStorage(); + // storage type will become memory, and storage files will not exist + // passing storage path will not affect how storage will initialize + it("6- memory_cPath", (done) => { Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - interval: 10000, - max_events: -1, + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.MEMORY, storage_path: "../test/customStorageDirectory/", }); - assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); - done(); - }); + shouldFilesExist(false); + validateStorageTypeAndPath(StorageTypes.MEMORY); + createData(); - it("9- Reset Storage While on Default Path /no-init", (done) => { - // will set to default storage path - storage.setStoragePath(); - assert.equal(storage.getStoragePath(), "../data/"); - // will set to undefined - storage.resetStorage(); - assert.equal(storage.getStoragePath(), undefined); - done(); - }); - - it("10- Recording to Storage with Default Storage Path /no-init", (done) => { - storage.resetStorage(); - - // Set to default storage path - storage.setStoragePath(); - assert.equal(storage.getStoragePath(), "../data/"); - recordValuesToStorageAndValidate(); - done(); - }); - - it("11- Recording to Storage with Custom Storage Path /no-init", (done) => { - storage.resetStorage(); - // will set to default storage path - storage.setStoragePath("../test/customStorageDirectory/"); - assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); - recordValuesToStorageAndValidate("../test/customStorageDirectory/"); - done(); + setTimeout(() => { + shouldFilesExist(false); + validateStorageTypeAndPath(StorageTypes.MEMORY); + validateData(false, true); + done(); + }, hp.mWait); }); - it("12- Recording to Bulk Storage with Default Bulk Data Path /no-init", (done) => { - storage.resetStorage(); - // will set to default storage path - // To set the storage path to the default bulk storage path and persist the queue - storage.setStoragePath(null, true, true); - assert.equal(storage.getStoragePath(), "../bulk_data/"); - recordValuesToStorageAndValidate(null, false, true, true); - done(); - }); + // if custom method is provided with memory storage type, sdk will switch to custom method + it("7- memory_cMethod", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + storage_type: StorageTypes.MEMORY, + custom_storage_method: testUtils.getCustomStorage(), + }); + shouldFilesExist(false); + // storage type will be null since custom method is provided + validateStorageTypeAndPath(null); + createData(); - it("13- Recording to Bulk Storage with Custom Bulk Storage Path /no-init", (done) => { - storage.resetStorage(); - // will set to default storage path - storage.setStoragePath("../test/customStorageDirectory/", true); - assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); - recordValuesToStorageAndValidate("../test/customStorageDirectory/", false, true); - done(); + setTimeout(() => { + shouldFilesExist(false); + validateStorageTypeAndPath(null); + validateData(false, true); + done(); + }, hp.mWait); }); - it("14- Setting storage path to default path via initStorage /no-init", (done) => { - storage.resetStorage(); - storage.initStorage(); - assert.equal(storage.getStoragePath(), "../data/"); - done(); - }); + // custom method is provided without any path or storage type information + // storage files will be created and will set the path as the default but sdk will use custom methods + it("8- custom_noPath", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + custom_storage_method: testUtils.getCustomStorage(), + }); + shouldFilesExist(true); + // storage type will be null since custom method is provided + validateStorageTypeAndPath(null); + createData(); - it("15- Setting bulk storage path to default path via initStorage /no-init", (done) => { - storage.resetStorage(); - storage.initStorage(null, false, true); - assert.equal(storage.getStoragePath(), "../bulk_data/"); - done(); + setTimeout(() => { + shouldFilesExist(true); + validateStorageTypeAndPath(null); + validateData(false, true); + done(); + }, hp.mWait); }); - it("16- Setting custom storage path via initStorage /no-init", (done) => { - storage.resetStorage(); - storage.initStorage("../test/customStorageDirectory/"); - assert.equal(storage.getStoragePath(), "../test/customStorageDirectory/"); - done(); - }); + // custom method is provided with custom path + // sdk will set the path as the custom and sdk will use the custom methods + it("9- custom_cPath", (done) => { + Countly.init({ + app_key: appKey, + url: serverUrl, + custom_storage_method: testUtils.getCustomStorage(), + storage_path: "../test/customStorageDirectory/", + }); + shouldFilesExist(true); + // storage type will be null since custom method is provided + // path will be the custom path in this case + validateStorageTypeAndPath(null, "../test/customStorageDirectory/"); + createData(); - it("17- Setting storage method to memory only and checking storage path /no-init", (done) => { - storage.resetStorage(); - storage.initStorage(null, StorageTypes.MEMORY); - assert.equal(storage.getStoragePath(), undefined); - done(); + setTimeout(() => { + shouldFilesExist(true); + validateStorageTypeAndPath(null, "../test/customStorageDirectory/"); + validateData(false, true); + done(); + }, hp.mWait); }); - it("18- Memory only storage Device-Id", (done) => { - hp.clearStorage(); + // custom storage method with invalid storage path + // sdk will not try to initialize the storage with invalid path and return to the default path + // storage files will exist, type will be null and use custom methods + it("10- custom_invalid_path", (done) => { Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - device_id: "Test-Device-Id", - clear_stored_device_id: true, - storage_type: StorageTypes.MEMORY, - }); - hp.doesFileStoragePathsExist((exists) => { - assert.equal(false, exists); + app_key: appKey, + url: serverUrl, + custom_storage_method: testUtils.getCustomStorage(), + storage_path: undefined, }); - assert.equal(storage.getStoragePath(), undefined); - assert.equal(storage.storeGet("cly_id", null), "Test-Device-Id"); - assert.equal(storage.storeGet("cly_id_type", null), cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); - done(); + shouldFilesExist(true); + // storage type will be null since custom method is provided + validateStorageTypeAndPath(null); + createData(); + + setTimeout(() => { + shouldFilesExist(true); + validateStorageTypeAndPath(null); + validateData(false, true); + done(); + }, hp.mWait); }); - it("19- Record event in memory only mode and validate the record", (done) => { - hp.clearStorage(); + // custom storage method with invalid methods + // sdk should not use the invalid methods and switch back to file storage + it("11- custom_invalid_cMethod", (done) => { Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - device_id: "Test-Device-Id", - clear_stored_device_id: true, - storage_type: StorageTypes.MEMORY, - }); - hp.doesFileStoragePathsExist((exists) => { - assert.equal(false, exists); + app_key: appKey, + url: serverUrl, + custom_storage_method: testUtils.getInvalidStorage(), }); - Countly.add_event(eventObj); + shouldFilesExist(true); + // storage type will be null since custom method is provided + validateStorageTypeAndPath(StorageTypes.FILE); + createData(); + setTimeout(() => { - const storedData = storage.storeGet("cly_queue", null); - const eventArray = JSON.parse(storedData[0].events); - const eventFromQueue = eventArray[0]; - hp.eventValidator(eventObj, eventFromQueue); + shouldFilesExist(true); + validateStorageTypeAndPath(StorageTypes.FILE); + validateData(); done(); }, hp.mWait); }); - it("20- Record and validate user details in memory only mode", (done) => { - hp.clearStorage(); - Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - device_id: "Test-Device-Id", - clear_stored_device_id: true, - storage_type: StorageTypes.MEMORY, - }); - hp.doesFileStoragePathsExist((exists) => { - assert.equal(false, exists); - }); - Countly.user_details(userDetailObj); - const storedData = storage.storeGet("cly_queue", null); - const userDetailsReq = storedData[0]; - hp.userDetailRequestValidator(userDetailObj, userDetailsReq); + // initStorage method without any parameters + // storage should initialize correctly and be ready to use + it("12- initStorage_noParams_noInit", (done) => { + storage.initStorage(); + shouldFilesExist(true); + // storage type will be File since it's default + validateStorageTypeAndPath(StorageTypes.FILE); + validateStorageMethods(); done(); }); - it("21- Memory only storage, change SDK Generated Device-Id", (done) => { - hp.clearStorage(); - Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - clear_stored_device_id: true, - storage_type: StorageTypes.MEMORY, - }); - hp.doesFileStoragePathsExist((exists) => { - assert.equal(false, exists); - }); - assert.equal(storage.getStoragePath(), undefined); - assert.equal(storage.storeGet("cly_id", null), Countly.get_device_id()); - assert.equal(storage.storeGet("cly_id_type", null), Countly.get_device_id_type()); - - Countly.change_id("Test-Id-2"); - assert.equal(storage.storeGet("cly_id", null), "Test-Id-2"); - assert.equal(storage.storeGet("cly_id_type", null), cc.deviceIdTypeEnums.DEVELOPER_SUPPLIED); + // initStorage method with File storage type and null path + // storage should initialize correctly with default path + it("13- initStorage_file_nullPath_noInit", (done) => { + storage.initStorage(null, StorageTypes.FILE); + shouldFilesExist(true); + // storage type will be File since it's default + validateStorageTypeAndPath(StorageTypes.FILE); + validateStorageMethods(); done(); }); - it("22- Switch to file storage after init", (done) => { - hp.clearStorage(); - Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://test.url.ly", - clear_stored_device_id: true, - storage_type: StorageTypes.MEMORY, - }); - assert.equal(storage.getStoragePath(), undefined); - assert.equal(storage.getStorageType(), StorageTypes.MEMORY); - hp.doesFileStoragePathsExist((exists) => { - assert.equal(false, exists); - }); - - storage.initStorage(); - assert.equal(storage.getStoragePath(), "../data/"); - assert.equal(storage.getStorageType(), StorageTypes.FILE); + // initStorage method with File storage type and custom path + // storage should initialize correctly with custom path + it("14- initStorage_file_cPath_noInit", (done) => { + storage.initStorage("../test/customStorageDirectory/", StorageTypes.FILE); + shouldFilesExist(true); + // storage type will be File since it's default + validateStorageTypeAndPath(StorageTypes.FILE, true); + validateStorageMethods(); done(); }); - it("23- storeRemove Memory Only /no-init", (done) => { - hp.clearStorage(); + // initStorage method with memory storage type and null path + // storage should initialize correctly with memory storage + it("15- initStorage_memory_noInit", (done) => { storage.initStorage(null, StorageTypes.MEMORY); - assert.equal(storage.getStoragePath(), undefined); - assert.equal(storage.getStorageType(), StorageTypes.MEMORY); - storage.storeSet("keyToStore", "valueToStore"); - assert.equal(storage.storeGet("keyToStore", null), "valueToStore"); - - storage.storeRemove("keyToStore"); - assert.equal(storage.storeGet("keyToStore", null), null); + shouldFilesExist(false); + validateStorageTypeAndPath(StorageTypes.MEMORY); + validateStorageMethods(); done(); }); - it("24- storeRemove File Storage /no-init", (done) => { - hp.clearStorage(); - storage.initStorage(); - assert.equal(storage.getStoragePath(), "../data/"); - assert.equal(storage.getStorageType(), StorageTypes.FILE); - storage.storeSet("keyToStore", "valueToStore"); - assert.equal(storage.storeGet("keyToStore", null), "valueToStore"); + // initStorage method with custom storage method and null path + it("16- initStorage_custom_nullPath_noInit", (done) => { + storage.initStorage(null, null, false, testUtils.getCustomStorage()); + shouldFilesExist(true); + validateStorageTypeAndPath(null); + validateStorageMethods(); + done(); + }); - storage.storeRemove("keyToStore"); - assert.equal(storage.storeGet("keyToStore", null), null); + // initStorage method with custom storage method and custom path + it("17- initStorage_custom_cPath_noInit", (done) => { + storage.initStorage("../test/customStorageDirectory/", null, false, testUtils.getCustomStorage()); + shouldFilesExist(true); + validateStorageTypeAndPath(null, true); + validateStorageMethods(); done(); }); }); \ No newline at end of file diff --git a/test/tests_user_details.js b/test/tests_user_details.js index 9e859ec..11b57b2 100644 --- a/test/tests_user_details.js +++ b/test/tests_user_details.js @@ -1,43 +1,26 @@ /* eslint-disable no-console */ +const assert = require("assert"); var Countly = require("../lib/countly"); var hp = require("./helpers/helper_functions"); - -var userDetailObj = { - name: "Barturiana Sosinsiava", - username: "bar2rawwen", - email: "test@test.com", - organization: "Dukely", - phone: "+123456789", - picture: "https://ps.timg.com/profile_images/52237/011_n_400x400.jpg", - gender: "Non-binary", - byear: 1987, // birth year - custom: { - "key1 segment": "value1 segment", - "key2 segment": "value2 segment", - }, -}; -// init function -function initMain() { - Countly.init({ - app_key: "YOUR_APP_KEY", - url: "https://try.count.ly", - interval: 10000, - max_events: -1, - }); -} +var testUtils = require("./helpers/test_utils"); describe("User details tests", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("Record and validate all user details", (done) => { - // clear previous data - hp.clearStorage(); - // initialize SDK - initMain(); - // send user details + Countly.init({ + app_key: "YOUR_APP_KEY", + url: "https://try.count.ly", + }); + var userDetailObj = testUtils.getUserDetailsObj(); Countly.user_details(userDetailObj); // read event queue setTimeout(() => { var req = hp.readRequestQueue()[0]; - hp.userDetailRequestValidator(userDetailObj, req); + const actualUserDetails = req.user_details; + const isValid = hp.validateUserDetails(actualUserDetails, userDetailObj); + assert.equal(true, isValid); done(); }, hp.sWait); }); diff --git a/test/tests_views.js b/test/tests_views.js index 664f3ca..07e6926 100644 --- a/test/tests_views.js +++ b/test/tests_views.js @@ -15,9 +15,10 @@ function initMain() { } describe("View test", () => { + beforeEach(async() => { + await hp.clearStorage(); + }); it("Record and validate page views", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); // send track view @@ -30,8 +31,6 @@ describe("View test", () => { }, hp.sWait); }); it("Record and validate timed page views with same name", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); Countly.track_view(pageNameOne); @@ -52,8 +51,6 @@ describe("View test", () => { }, hp.mWait); }); it("Record and validate timed page views with same name", (done) => { - // clear previous data - hp.clearStorage(); // initialize SDK initMain(); Countly.track_view(pageNameOne);