Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Custom Storage Option #104

Merged
merged 21 commits into from
Oct 21, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- Added a new init time config option (conf.storage_type) which can make user set among these storage options:
- File Storage
- Memory Only Storage
- Custom Storage Methods
- Added a new init time config option (conf.custom_storage_method) which enables user to provide custom storage methods.
AliRKat marked this conversation as resolved.
Show resolved Hide resolved

## 22.06.0
- Fixed a bug where remote config requests were rejected
Expand Down
13 changes: 12 additions & 1 deletion lib/countly-storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,22 @@ const fileStorage = {
* @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
* @param {varies} customStorageMethod - Storage methods provided by the user
*/
var initStorage = function(userPath, storageType, isBulk = false, persistQueue = false) {
var initStorage = function(userPath, storageType, isBulk = false, persistQueue = false, customStorageMethod = null) {
if (storageType === StorageTypes.MEMORY) {
storageMethod = memoryStorage;
}
else if (storageType === StorageTypes.CUSTOM) {
if (customStorageMethod) {
storageMethod = customStorageMethod;
arifBurakDemiray marked this conversation as resolved.
Show resolved Hide resolved
}
else {
cc.log(cc.logLevelEnums.WARNING, `Provided Custom Storage Methods are not valid. Switching to default file storage!`);
storageMethod = fileStorage;
setStoragePath(userPath, isBulk, persistQueue);
}
}
else {
storageMethod = fileStorage;
setStoragePath(userPath, isBulk, persistQueue);
Expand Down
3 changes: 2 additions & 1 deletion lib/countly.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,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 {varies} conf.custom_storage_method - user given storage methods
* @example
* Countly.init({
* app_key: "{YOUR-APP-KEY}",
Expand Down Expand Up @@ -172,7 +173,7 @@ Countly.Bulk = Bulk;
// 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, false, conf.custom_storage_method);
AliRKat marked this conversation as resolved.
Show resolved Hide resolved

// clear stored device ID if flag is set
if (conf.clear_stored_device_id) {
Expand Down
161 changes: 160 additions & 1 deletion test/tests_storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,53 @@ function recordValuesToStorageAndValidate(userPath, memoryOnly = false, isBulk =
assert.equal(storage.storeGet("cly_object"), undefined);
assert.equal(storage.storeGet("cly_null"), undefined);
}
const funkyMemoryStorage = {
_storage: {},

storeSet: function(key, value, callback) {
if (key) {
const existingValue = this._storage[key];
if (typeof value === 'string' && typeof existingValue === 'string') {
this._storage[key] = existingValue + value;
}
else {
this._storage[key] = value;
}
if (typeof callback === "function") {
callback(null);
}
}
},
storeGet: function(key, def) {
const value = this._storage[key];
if (typeof value === 'string') {
return value.split('').reverse().join('');
}

return value !== undefined ? value : def;
},
storeRemove: function(key) {
delete this._storage[key];
},
};

const customMemoryStorage = {
_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];
},
};

describe("Storage Tests", () => {
it("1- Store Generated Device ID", (done) => {
Expand Down Expand Up @@ -225,6 +272,8 @@ describe("Storage Tests", () => {
done();
});

// resets the storage path to default and validates that it is set correctly,
// then resets it to undefined and confirms the reset.
it("9- Reset Storage While on Default Path /no-init", (done) => {
// will set to default storage path
storage.setStoragePath();
Expand All @@ -235,16 +284,19 @@ describe("Storage Tests", () => {
done();
});

// sets the storage path to default and verifies it,
// then records values to storage and ensures they are stored correctly.
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();
});

// sets a custom storage path and verifies it,
// then records values to storage and ensures correct storage in the custom path.
it("11- Recording to Storage with Custom Storage Path /no-init", (done) => {
storage.resetStorage();
// will set to default storage path
Expand All @@ -254,6 +306,8 @@ describe("Storage Tests", () => {
done();
});

// sets the storage path to the default bulk storage path and verifies it,
// then records values to bulk storage and validates proper storage in bulk mode.
it("12- Recording to Bulk Storage with Default Bulk Data Path /no-init", (done) => {
storage.resetStorage();
// will set to default storage path
Expand All @@ -264,6 +318,8 @@ describe("Storage Tests", () => {
done();
});

// sets a custom bulk storage path and verifies it,
// then records values to bulk storage and ensures proper recording to the custom path.
it("13- Recording to Bulk Storage with Custom Bulk Storage Path /no-init", (done) => {
storage.resetStorage();
// will set to default storage path
Expand Down Expand Up @@ -301,6 +357,9 @@ describe("Storage Tests", () => {
done();
});

// recording device-id in memory only mode
// initializes the SDK in memory only mode, validates that file storage files does not exist
// retrieve the developer supplied device id and id type from storage
it("18- Memory only storage Device-Id", (done) => {
hp.clearStorage();
Countly.init({
Expand All @@ -319,6 +378,9 @@ describe("Storage Tests", () => {
done();
});

// recording event in memory only mode
// initializes the SDK in memory only mode, validates that file storage files does not exist
// records an event and validates the recorded event
it("19- Record event in memory only mode and validate the record", (done) => {
hp.clearStorage();
Countly.init({
Expand All @@ -341,6 +403,9 @@ describe("Storage Tests", () => {
}, hp.mWait);
});

// recording user details in memory only mode
// initializes the SDK in memory only mode, validates that file storage files does not exist
// records user details and validates the recorded details
it("20- Record and validate user details in memory only mode", (done) => {
hp.clearStorage();
Countly.init({
Expand All @@ -360,6 +425,9 @@ describe("Storage Tests", () => {
done();
});

// tests device id changes in memory only storage
// initialize the SDK in memory only mode, check the device id and switch it
// SDK and storage should function properly
it("21- Memory only storage, change SDK Generated Device-Id", (done) => {
hp.clearStorage();
Countly.init({
Expand All @@ -381,6 +449,9 @@ describe("Storage Tests", () => {
done();
});

// tests switching between storage types after initializing SDK
// passing memory storage type during init and initializing storage afterwards
// SDK should switch to file storage
it("22- Switch to file storage after init", (done) => {
hp.clearStorage();
Countly.init({
Expand All @@ -401,6 +472,9 @@ describe("Storage Tests", () => {
done();
});

// tests storeRemove function in CountlyStorage
// after initializing the memory storage, without initializing SDK, attempts to set, get and remove values
// without initializing SDK storage should function properly
it("23- storeRemove Memory Only /no-init", (done) => {
hp.clearStorage();
storage.initStorage(null, StorageTypes.MEMORY);
Expand All @@ -414,6 +488,9 @@ describe("Storage Tests", () => {
done();
});

// tests storeRemove function in CountlyStorage
// after initializing the file storage, without initializing SDK attempts to set, get and remove values
// without initializing SDK storage should function properly
it("24- storeRemove File Storage /no-init", (done) => {
hp.clearStorage();
storage.initStorage();
Expand All @@ -426,4 +503,86 @@ describe("Storage Tests", () => {
assert.equal(storage.storeGet("keyToStore", null), null);
done();
});

// tests init time storage config options
// choosing Custom storage type and passing null in storage methods
// passing null as storage method ends up with switching to default file storage
it("25- Null Custom Storage Method", (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.CUSTOM,
custom_storage_method: null,
});
assert.equal(storage.getStoragePath(), "../data/");
assert.equal(storage.getStorageType(), StorageTypes.FILE);
done();
});

// tests init time storage config options
// choosing Custom storage type and passing custom storage methods
// SDK should use custom methods as storage method, no File Storage should exist
it("26- Providing Custom Storage Method", (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.CUSTOM,
custom_storage_method: customMemoryStorage,
});
hp.doesFileStoragePathsExist((exists) => {
assert.equal(false, exists);
});
done();
});

// tests init time storage config options
// Recording values in Custom Storage Methods
// SDK should use custom methods as storage methods and values should be recorded correctly
it("27- Record/Remove Values in Custom Storage Method", (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.CUSTOM,
custom_storage_method: customMemoryStorage,
});
hp.doesFileStoragePathsExist((exists) => {
assert.equal(false, exists);
});
storage.storeSet("CustomStorageKey", "CustomStorageValue");
assert.equal(storage.storeGet("CustomStorageKey", null), "CustomStorageValue");
storage.storeRemove("CustomStorageKey");
assert.equal(storage.storeGet("CustomStorageKey", null), null);
done();
});

// tests init time storage config options
// passes a funky storage method, which does store get as reversing string
// SDK should use custom methods as storage method
it("28- Record/Remove Values in Custom Storage Method", (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.CUSTOM,
custom_storage_method: funkyMemoryStorage,
});
hp.doesFileStoragePathsExist((exists) => {
assert.equal(false, exists);
});
storage.storeSet("CustomStorageKey", "CustomStorageValue");
storage.storeSet("CustomStorageKey", "CustomStorageValue2");
assert.equal("2eulaVegarotSmotsuCeulaVegarotSmotsuC", storage.storeGet("CustomStorageKey", null));
done();
});
});
Loading