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

First version of the code to add the feature OnFieldChangeRepeat, thi… #145

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 153 additions & 0 deletions client/src/OnDeviceComponent.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2195,6 +2195,159 @@ describe('OnDeviceComponent', function () {
});
});

describe('onFieldChangeRepeat', function () {
it('check multiple onFieldChangeRepeat requests running simultaneously, match tested', async () => {

//RESET COUNTER
await odc.setValue({
base: 'global',
keyPath: 'repeatingTimerFireCount',
value: 0,
});

const args1 = { base: 'global', keyPath: 'repeatingTimerFireCount' } as ODC.BaseKeyPath;
const args2 = { base: 'global', keyPath: 'repeatingTimerFireCount', match: 2 } as ODC.BaseKeyPath;
const args3 = { base: 'global', keyPath: 'repeatingTimerFireCount', match: 7 } as ODC.BaseKeyPath;

let events1: object[] = [];
let events2: object[] = [];
let events3: object[] = [];

const cancelRequest1 = await odc.onFieldChangeRepeat({ ...args1 },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events1.push(response);
}
return;
});

const cancelRequest2 = await odc.onFieldChangeRepeat({ ...args2 },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events2.push(response);
}
return;
});

const cancelRequest3 = await odc.onFieldChangeRepeat({ ...args3 },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events3.push(response);
}
return;
});

await utils.sleep(3000);
expect(events1.length).to.be.lessThan(4); //We expect to have 3

let cancelResponse1 = await cancelRequest1();
let cancelMessage1 = cancelResponse1.json.success.message;
expect(cancelMessage1).to.be.a('string');

await utils.sleep(3*1000);
expect(events1.length).to.be.lessThan(4); //We expect to have 3

let cancelResponse2 = await cancelRequest2();
let cancelMessage2 = cancelResponse2.json.success.message;
expect(cancelMessage2).to.be.a('string');
await utils.sleep(2*1000);

let cancelResponse3 = await cancelRequest3();
let cancelMessage3 = cancelResponse3.json.success.message;
expect(cancelMessage3).to.be.a('string');
expect(events1.length).to.be.lessThan(4); //We expect to have 6
expect(events2.length).to.be.equal(1); //We expect to have 1
expect(events3.length).to.be.equal(1); //We expect to have 1
});

it('should only receive the event where the value is 3', async () => {

//RESET COUNTER
await odc.setValue({
base: 'global',
keyPath: 'repeatingTimerFireCount',
value: 0,
});

const args = { base: 'global', keyPath: 'repeatingTimerFireCount', match: 3 } as ODC.BaseKeyPath;
let events: object[] = [];
const cancelRequest = await odc.onFieldChangeRepeat({ ...args },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events.push(response);
}
return;
});
await utils.sleep(4*1000);
expect(events).to.be.an('array');
expect(events).to.have.lengthOf(1);
let cancelResult = await cancelRequest();
let cancelMessage = cancelResult.json.success.message;
expect(cancelMessage).to.be.a('string');
});

it('should have some events on the events array', async () => {
const args = { base: 'global', keyPath: 'repeatingTimerFireCount' } as ODC.BaseKeyPath;
let events: object[] = [];
const cancelRequest = await odc.onFieldChangeRepeat({ ...args },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events.push(response);
}
return;
});
await utils.sleep(3*1000);
expect(events).to.be.an('array');
expect(events).to.not.be.empty;
let cancelResult = await cancelRequest();
let cancelMessage = cancelResult.json.success.message;
expect(cancelMessage).to.be.a('string');
});

it('check multiple onFieldChangeRepeat requests running simultaneously', async () => {

//RESET COUNTER
await odc.setValue({
base: 'global',
keyPath: 'repeatingTimerFireCount',
value: 0,
});

//Same args for both requests
const args = { base: 'global', keyPath: 'repeatingTimerFireCount' } as ODC.BaseKeyPath;

let events1: object[] = [];
let events2: object[] = [];

const cancelRequest1 = await odc.onFieldChangeRepeat({ ...args },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events1.push(response);
}
return;
});

const cancelRequest2 = await odc.onFieldChangeRepeat({ ...args },{timeout:(20*1000)}, (response)=>{
if(response["json"] != null && response.json["value"]!= null){
events2.push(response);
}
return;
});

await utils.sleep(3*1000); //Wait 3 seconds
expect(events1).to.be.an('array');
expect(events1).to.not.be.empty;

let cancelResponse1 = await cancelRequest1();
let cancelMessage1 = cancelResponse1.json.success.message;
expect(cancelMessage1).to.be.a('string');

await utils.sleep(4*1000); //Wait more 3 seconds
expect(events2).to.be.an('array');
expect(events2.length).to.be.greaterThan(3); //We expect to have 6

let cancelResponse2 = await cancelRequest2();
let cancelMessage2 = cancelResponse2.json.success.message;
expect(cancelMessage2).to.be.a('string');

});

});

describe('callFunc', function () {
it('should fail if given invalid keyPath', async () => {
try {
Expand Down
74 changes: 72 additions & 2 deletions client/src/OnDeviceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,60 @@ export class OnDeviceComponent {
} & ODC.ReturnTimeTaken;
}

//This function onFieldChangeRepeat have a lot of repeated code, we may could create a common function to handle the common thing and just change the output based on if it has callback or not
public async onFieldChangeRepeat(args: ODC.OnFieldChangeOnceArgs, options: ODC.RequestOptions = {}, callback: (response) => void) {
this.conditionallyAddDefaultBase(args);
this.conditionallyAddDefaultNodeReferenceKey(args);
args = this.breakOutFieldFromKeyPath(args);

const match = args.match;
if (match !== undefined) {
// Check if it's an object. Also have to check constructor as array is also an instanceof Object, make sure it has the keyPath key
if (((match instanceof Object) && (match.constructor.name === 'Object') && ('keyPath' in match))) {
this.conditionallyAddDefaultBase(match);
} else {
// If it's not we take base and keyPath from the base, keyPath and field args
args.match = {
base: args.base,
keyPath: args.keyPath,
field: args.field,
value: (match as any)
};
}
}

if (!args.retryInterval) args.retryInterval = 100;

const deviceConfig = this.device.getCurrentDeviceConfig();
let retryTimeout: number;

if (args.retryTimeout !== undefined) {
retryTimeout = args.retryTimeout;
// Adding a reasonable amount of time so that we get a more specific error message instead of the generic timeout
options.timeout = retryTimeout + 200;
} else {
retryTimeout = options.timeout ?? deviceConfig.defaultTimeout ?? this.defaultTimeout;
retryTimeout -= 200;
}

const multiplier = deviceConfig.timeoutMultiplier ?? 1;
retryTimeout *= multiplier;

args.retryTimeout = retryTimeout;

//We wait because we need the result of the sendRequest to create the cancelObserverCallback
const result = await this.sendRequest(ODC.RequestType.onFieldChangeRepeat, args, options, callback);
//We return the cancel Observer Function to easily cancel the continous observer
const cancelObserverFunc = async () => {
//Is json.id the correct place to extract the request ID?
//const requestID = result.json.id
const cancelStatus = await this.sendRequest(ODC.RequestType.cancelOnFieldChangeRepeat, {cancelRequestId: result.json.id});
return cancelStatus;
}

return cancelObserverFunc;
}

public async setValue(args: ODC.SetValueArgs, options: ODC.RequestOptions = {}) {
this.conditionallyAddDefaultBase(args);
this.conditionallyAddDefaultNodeReferenceKey(args);
Expand Down Expand Up @@ -862,7 +916,19 @@ export class OnDeviceComponent {
if (callback) {
callback(receivingRequestResponse);
}
delete this.activeRequests[json.id];

//Delete active requests as usual if it is not of type onFieldChangeRepeat
if (request.type != 'onFieldChangeRepeat'){
delete this.activeRequests[json.id];
}

// On the case of request.type onFieldChangeRepeat we just proceed for deletion if it has request.args.cancelRequestId
if (request.type == 'cancelOnFieldChangeRepeat'){
if((request["args"] != null && request.args["cancelRequestId"] != null && this.activeRequests[request.args["cancelRequestId"]] != null)){
delete this.activeRequests[request.args["cancelRequestId"]];
}
}

}
}
});
Expand All @@ -877,7 +943,7 @@ export class OnDeviceComponent {
return this.clientSocketPromise;
}

private async sendRequest(type: ODC.RequestType, args: ODC.RequestArgs, options: ODC.RequestOptions = {}) {
private async sendRequest(type: ODC.RequestType, args: ODC.RequestArgs, options: ODC.RequestOptions = {}, callback?) {
const requestId = utils.randomStringGenerator();
const request: ODC.Request = {
id: requestId,
Expand Down Expand Up @@ -942,6 +1008,10 @@ export class OnDeviceComponent {
this.debugLog('Received response:', response.json);
if (json?.error === undefined) {
resolve(response);
//Important to send response to the callback when onFieldChangeRepeat is used
if (callback){
callback(response);
};
} else {
let error: Error;
if (stackTraceError) {
Expand Down
10 changes: 9 additions & 1 deletion client/src/types/OnDeviceComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Socket } from 'net';

export enum RequestType {
callFunc = 'callFunc',
cancelOnFieldChangeRepeat = 'cancelOnFieldChangeRepeat',
createDirectory = 'createDirectory',
createChild = 'createChild',
deleteFile = 'deleteFile',
Expand All @@ -27,6 +28,7 @@ export enum RequestType {
isSubtype = 'isSubtype',
isShowingOnScreen = 'isShowingOnScreen',
onFieldChangeOnce = 'onFieldChangeOnce',
onFieldChangeRepeat = 'onFieldChangeRepeat',
readFile = 'readFile',
readRegistry = 'readRegistry',
removeNode = 'removeNode',
Expand All @@ -43,7 +45,7 @@ export enum RequestType {
writeRegistry = 'writeRegistry',
}

export type RequestArgs = CallFuncArgs | CreateChildArgs | GetFocusedNodeArgs | GetValueArgs | GetValuesArgs | HasFocusArgs | IsInFocusChainArgs | OnFieldChangeOnceArgs | SetValueArgs | ReadRegistryArgs | WriteRegistryArgs | DeleteRegistrySectionsArgs | DeleteEntireRegistrySectionsArgs | StoreNodeReferencesArgs | GetNodesInfoArgs | FindNodesAtLocationArgs | CreateDirectoryArgs | DeleteEntireRegistrySectionsArgs | DeleteFileArgs | DeleteNodeReferencesArgs | DisableScreensaverArgs | FocusNodeArgs | GetAllCountArgs | GetDirectoryListingArgs | GetNodesWithPropertiesArgs | GetRootsCountArgs | GetServerHostArgs | GetVolumeListArgs | IsShowingOnScreenArgs | IsSubtypeArgs | ReadFileArgs | RenameFileArgs | SetSettingsArgs | StartResponsivenessTestingArgs | StatPathArgs | WriteFileArgs | RemoveNodeArgs |RemoveNodeChildrenArgs | DisableScreensaverArgs;
export type RequestArgs = CallFuncArgs | CreateChildArgs | GetFocusedNodeArgs | GetValueArgs | GetValuesArgs | HasFocusArgs | IsInFocusChainArgs | OnFieldChangeOnceArgs | CancelOnFieldChangeRepeatArgs | SetValueArgs | ReadRegistryArgs | WriteRegistryArgs | DeleteRegistrySectionsArgs | DeleteEntireRegistrySectionsArgs | StoreNodeReferencesArgs | GetNodesInfoArgs | FindNodesAtLocationArgs | CreateDirectoryArgs | DeleteEntireRegistrySectionsArgs | DeleteFileArgs | DeleteNodeReferencesArgs | DisableScreensaverArgs | FocusNodeArgs | GetAllCountArgs | GetDirectoryListingArgs | GetNodesWithPropertiesArgs | GetRootsCountArgs | GetServerHostArgs | GetVolumeListArgs | IsShowingOnScreenArgs | IsSubtypeArgs | ReadFileArgs | RenameFileArgs | SetSettingsArgs | StartResponsivenessTestingArgs | StatPathArgs | WriteFileArgs | RemoveNodeArgs |RemoveNodeChildrenArgs | DisableScreensaverArgs;

export enum BaseType {
global = 'global',
Expand Down Expand Up @@ -390,6 +392,12 @@ export interface OnFieldChangeOnceArgs extends BaseKeyPath {
match?: MatchObject | ComparableValueTypes;
}

//We may could use OnFieldChangeArgs to avoid more code but to keep the pattern we've created this
export interface CancelOnFieldChangeRepeatArgs extends BaseKeyPath {
/** requestId that should be canceled */
cancelRequestId: string;
}

export interface SetValueArgs extends BaseKeyPath {
/** Value that needs to be set to the field. Field path is defined by key path. */
value: any;
Expand Down
Loading