-
Notifications
You must be signed in to change notification settings - Fork 83
fix: fixes caching for nested objects and adds tests #3873
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -123,7 +123,9 @@ const generateCacheKey = (methodName: string, args: IArgument[]) => { | |
if (value?.constructor?.name != 'RequestDetails') { | ||
if (value && typeof value === 'object') { | ||
for (const [key, innerValue] of Object.entries(value)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, now that we are using |
||
cacheKey += `_${key}_${innerValue}`; | ||
const serializedValue = | ||
typeof innerValue === 'object' && innerValue !== null ? JSON.stringify(innerValue) : innerValue; | ||
cacheKey += `_${key}_${serializedValue}`; | ||
} | ||
continue; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -107,13 +107,13 @@ describe('cache decorator', () => { | |
|
||
describe('shouldSkipCachingForSingleParams', () => { | ||
it('should return false if no skip rules are provided', () => { | ||
const args = ['safe', 'latest'] as unknown as IArguments; | ||
const args = ['safe', 'latest'] as unknown as IArguments[]; | ||
const result = __test__.__private.shouldSkipCachingForSingleParams(args, []); | ||
expect(result).to.be.false; | ||
}); | ||
|
||
it('should return false if argument exists but is not in skip values', () => { | ||
const args = ['latest', 'earliest'] as unknown as IArguments; | ||
const args = ['latest', 'earliest'] as unknown as IArguments[]; | ||
const params = [ | ||
{ index: '0', value: 'pending' }, | ||
{ index: '1', value: 'safe|finalized' }, | ||
|
@@ -123,21 +123,21 @@ describe('cache decorator', () => { | |
}); | ||
|
||
it('should return true if a param at index matches any value in the pipe-separated list', () => { | ||
const args = ['earliest', 'safe'] as unknown as IArguments; | ||
const args = ['earliest', 'safe'] as unknown as IArguments[]; | ||
const params = [{ index: '1', value: 'pending|safe' }]; | ||
const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); | ||
expect(result).to.be.true; | ||
}); | ||
|
||
it('should return true if the argument at index is missing (do not cache optional parameters)', () => { | ||
const args = ['latest'] as unknown as IArguments; | ||
const args = ['latest'] as unknown as IArguments[]; | ||
const params = [{ index: '1', value: 'pending|safe' }]; | ||
const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); | ||
expect(result).to.be.true; | ||
}); | ||
|
||
it('should return true if the argument at index is explicitly undefined', () => { | ||
const args = ['finalized', undefined] as unknown as IArguments; | ||
const args = ['finalized', undefined] as unknown as IArguments[]; | ||
const params = [{ index: '1', value: 'pending|safe' }]; | ||
const result = __test__.__private.shouldSkipCachingForSingleParams(args, params); | ||
expect(result).to.be.true; | ||
|
@@ -146,13 +146,13 @@ describe('cache decorator', () => { | |
|
||
describe('shouldSkipCachingForNamedParams', () => { | ||
it('should return false when no rules are provided', () => { | ||
const args = [{ fromBlock: 'safe' }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'safe' }] as unknown as IArguments[]; | ||
const result = __test__.__private.shouldSkipCachingForNamedParams(args, []); | ||
expect(result).to.be.false; | ||
}); | ||
|
||
it('should return false if the field value does not match skip values', () => { | ||
const args = [{ fromBlock: 'confirmed' }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'confirmed' }] as unknown as IArguments[]; | ||
const params = [ | ||
{ | ||
index: '0', | ||
|
@@ -164,7 +164,7 @@ describe('cache decorator', () => { | |
}); | ||
|
||
it('should return false if none of the multiple fields match', () => { | ||
const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'finalized', toBlock: 'earliest' }] as unknown as IArguments[]; | ||
const params = [ | ||
{ | ||
index: '0', | ||
|
@@ -179,7 +179,7 @@ describe('cache decorator', () => { | |
}); | ||
|
||
it('should return true if a field matches one of the skip values', () => { | ||
const args = [{ fromBlock: 'pending' }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'pending' }] as unknown as IArguments[]; | ||
const params = [ | ||
{ | ||
index: '0', | ||
|
@@ -191,7 +191,7 @@ describe('cache decorator', () => { | |
}); | ||
|
||
it('should return true if multiple fields are specified and one matches', () => { | ||
const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'earliest', toBlock: 'latest' }] as unknown as IArguments[]; | ||
const params = [ | ||
{ | ||
index: '0', | ||
|
@@ -208,21 +208,21 @@ describe('cache decorator', () => { | |
|
||
describe('generateCacheKey', () => { | ||
it('should return only the method name when args are empty', () => { | ||
const args = [] as unknown as IArguments; | ||
const args = [] as unknown as IArguments[]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we apply the suggestion left in |
||
|
||
const result = __test__.__private.generateCacheKey('eth_getBalance', args); | ||
expect(result).to.equal('eth_getBalance'); | ||
}); | ||
|
||
it('should append primitive arguments to the cache key', () => { | ||
const args = ['0xabc', 'latest'] as unknown as IArguments; | ||
const args = ['0xabc', 'latest'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_getBalance', args); | ||
expect(result).to.equal('eth_getBalance_0xabc_latest'); | ||
}); | ||
|
||
it('should append object key-value pairs to the cache key', () => { | ||
const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown as IArguments; | ||
const args = [{ fromBlock: 'earliest', toBlock: 5644 }] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_getLogs', args); | ||
expect(result).to.equal('eth_getLogs_fromBlock_earliest_toBlock_5644'); | ||
|
@@ -233,54 +233,135 @@ describe('cache decorator', () => { | |
constructor: { name: 'RequestDetails' }, | ||
someField: 'shouldBeIgnored', | ||
}; | ||
const args = [mockRequestDetails, 'earliest'] as unknown as IArguments; | ||
const args = [mockRequestDetails, 'earliest'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_call', args); | ||
expect(result).to.equal('eth_call_earliest'); | ||
}); | ||
|
||
it('should not skip null or undefined args', () => { | ||
const args = [undefined, null, 'pending'] as unknown as IArguments; | ||
const args = [undefined, null, 'pending'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_call', args); | ||
expect(result).to.equal('eth_call_undefined_null_pending'); | ||
}); | ||
|
||
it('should process multiple arguments correctly', () => { | ||
const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown as IArguments; | ||
const args = [{ fromBlock: '0xabc' }, 5644, 'safe'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_getLogs', args); | ||
expect(result).to.equal('eth_getLogs_fromBlock_0xabc_5644_safe'); | ||
}); | ||
|
||
it('should work with mixed types including booleans and numbers', () => { | ||
const args = [true, 42, { fromBlock: 'safe' }] as unknown as IArguments; | ||
const args = [true, 42, { fromBlock: 'safe' }] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('custom_method', args); | ||
expect(result).to.equal('custom_method_true_42_fromBlock_safe'); | ||
}); | ||
|
||
it('should serialize nested objects using JSON.stringify', () => { | ||
const args = [ | ||
{ | ||
tracer: 'callTracer', | ||
tracerConfig: { onlyTopCall: true }, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('debug_traceTransaction', args); | ||
expect(result).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); | ||
}); | ||
|
||
it('should differentiate between different nested object values', () => { | ||
const args1 = [ | ||
{ | ||
tracer: 'callTracer', | ||
tracerConfig: { onlyTopCall: true }, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const args2 = [ | ||
{ | ||
tracer: 'callTracer', | ||
tracerConfig: { onlyTopCall: false }, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const result1 = __test__.__private.generateCacheKey('debug_traceTransaction', args1); | ||
const result2 = __test__.__private.generateCacheKey('debug_traceTransaction', args2); | ||
|
||
expect(result1).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":true}'); | ||
expect(result2).to.equal('debug_traceTransaction_tracer_callTracer_tracerConfig_{"onlyTopCall":false}'); | ||
expect(result1).to.not.equal(result2); | ||
}); | ||
|
||
it('should handle nested objects with multiple properties', () => { | ||
const args = [ | ||
{ | ||
config: { | ||
timeout: 5000, | ||
retries: 3, | ||
options: { debug: true }, | ||
}, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('test_method', args); | ||
expect(result).to.equal('test_method_config_{"timeout":5000,"retries":3,"options":{"debug":true}}'); | ||
}); | ||
|
||
it('should handle nested arrays in objects', () => { | ||
const args = [ | ||
{ | ||
filter: { | ||
addresses: ['0x123', '0x456'], | ||
topics: [null, '0xabc'], | ||
}, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('eth_getLogs', args); | ||
expect(result).to.equal('eth_getLogs_filter_{"addresses":["0x123","0x456"],"topics":[null,"0xabc"]}'); | ||
}); | ||
|
||
it('should handle deeply nested objects', () => { | ||
const args = [ | ||
{ | ||
level1: { | ||
level2: { | ||
level3: { | ||
value: 'deep', | ||
}, | ||
}, | ||
}, | ||
}, | ||
] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.generateCacheKey('deep_method', args); | ||
expect(result).to.equal('deep_method_level1_{"level2":{"level3":{"value":"deep"}}}'); | ||
}); | ||
}); | ||
|
||
describe('extractRequestDetails', () => { | ||
it('should return the RequestDetails instance if found in args', () => { | ||
const requestDetails = new RequestDetails({ requestId: 'abc123', ipAddress: '127.0.0.1' }); | ||
const args = [5644, requestDetails, 'other'] as unknown as IArguments; | ||
const args = [5644, requestDetails, 'other'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.extractRequestDetails(args); | ||
expect(result.requestId).to.equal('abc123'); | ||
expect(result.ipAddress).to.equal('127.0.0.1'); | ||
}); | ||
|
||
it('should return a new default RequestDetails if not found', () => { | ||
const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown as IArguments; | ||
const args = [5644, { fromBlock: 'pending' }, 'value'] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.extractRequestDetails(args); | ||
expect(result.requestId).to.equal(''); | ||
expect(result.ipAddress).to.equal(''); | ||
}); | ||
|
||
it('should return new RequestDetails when args is empty', () => { | ||
const args = [] as unknown as IArguments; | ||
const args = [] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.extractRequestDetails(args); | ||
expect(result.requestId).to.equal(''); | ||
|
@@ -290,14 +371,14 @@ describe('cache decorator', () => { | |
it('should return the first RequestDetails instance if multiple are present', () => { | ||
const rd1 = new RequestDetails({ requestId: 'first', ipAddress: '1.1.1.1' }); | ||
const rd2 = new RequestDetails({ requestId: 'second', ipAddress: '2.2.2.2' }); | ||
const args = [rd1, rd2] as unknown as IArguments; | ||
const args = [rd1, rd2] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.extractRequestDetails(args); | ||
expect(result).to.equal(rd1); | ||
}); | ||
|
||
it('should handle null or undefined values in args', () => { | ||
const args = [undefined, null, 5644] as unknown as IArguments; | ||
const args = [undefined, null, 5644] as unknown as IArguments[]; | ||
|
||
const result = __test__.__private.extractRequestDetails(args); | ||
expect(result.requestId).to.equal(''); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can improve the type of
generateCacheKey
by usingunknown
Please note that this is not strictly about the issue mentioned in the PR. However, given that in the tests (see below) we type casting to use this function, maybe we can change it here.
Also, we can change the outer loop from
to
right?