Skip to content

Commit f6e82f7

Browse files
committed
update test
1 parent e81b3e6 commit f6e82f7

4 files changed

Lines changed: 521 additions & 32 deletions

File tree

cache/__tests__/cache-metrics.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
# set -e
1919

2020
# Configuration
21-
BASE_URL="${BASE_URL:-https://devstore.rerum.io}"
21+
BASE_URL="${BASE_URL:-http://localhost:3001}"
2222
API_BASE="${BASE_URL}/v1"
2323
# Auth token will be prompted from user
2424
AUTH_TOKEN=""
@@ -379,6 +379,14 @@ fill_cache() {
379379
rm /tmp/cache_fill_results_$$.tmp
380380
fi
381381

382+
# Ensure variables are clean integers (strip any whitespace/newlines)
383+
batch_success=$(echo "$batch_success" | tr -d '\n\r' | grep -o '[0-9]*' | head -1)
384+
batch_timeout=$(echo "$batch_timeout" | tr -d '\n\r' | grep -o '[0-9]*' | head -1)
385+
batch_fail=$(echo "$batch_fail" | tr -d '\n\r' | grep -o '[0-9]*' | head -1)
386+
batch_success=${batch_success:-0}
387+
batch_timeout=${batch_timeout:-0}
388+
batch_fail=${batch_fail:-0}
389+
382390
successful_requests=$((successful_requests + batch_success))
383391
timeout_requests=$((timeout_requests + batch_timeout))
384392
failed_requests=$((failed_requests + batch_fail))

cache/__tests__/cache.test.js

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,3 +706,286 @@ describe('Cache Statistics', () => {
706706
expect(stats.length).toBe(initialSize + 1)
707707
})
708708
})
709+
710+
describe('Cache Invalidation Tests', () => {
711+
beforeEach(async () => {
712+
await cache.clear()
713+
})
714+
715+
afterEach(async () => {
716+
await cache.clear()
717+
})
718+
719+
describe('invalidateByObject', () => {
720+
it('should invalidate matching query caches when object is created', async () => {
721+
// Cache a query for type=TestObject
722+
const queryKey = cache.generateKey('query', { body: { type: 'TestObject' } })
723+
await cache.set(queryKey, [{ id: '1', type: 'TestObject' }])
724+
725+
// Verify cache exists
726+
let cached = await cache.get(queryKey)
727+
expect(cached).toBeTruthy()
728+
729+
// Create new object that matches the query
730+
const newObj = { id: '2', type: 'TestObject', name: 'Test' }
731+
const invalidatedKeys = new Set()
732+
const count = await cache.invalidateByObject(newObj, invalidatedKeys)
733+
734+
// Verify cache was invalidated
735+
expect(count).toBe(1)
736+
expect(invalidatedKeys.has(queryKey)).toBe(true)
737+
cached = await cache.get(queryKey)
738+
expect(cached).toBeNull()
739+
})
740+
741+
it('should not invalidate non-matching query caches', async () => {
742+
// Cache a query for type=OtherObject
743+
const queryKey = cache.generateKey('query', { body: { type: 'OtherObject' } })
744+
await cache.set(queryKey, [{ id: '1', type: 'OtherObject' }])
745+
746+
// Create object that doesn't match
747+
const newObj = { id: '2', type: 'TestObject' }
748+
const count = await cache.invalidateByObject(newObj)
749+
750+
// Verify cache was NOT invalidated
751+
expect(count).toBe(0)
752+
const cached = await cache.get(queryKey)
753+
expect(cached).toBeTruthy()
754+
})
755+
756+
it('should invalidate search caches', async () => {
757+
const searchKey = cache.generateKey('search', { body: { type: 'TestObject' } })
758+
await cache.set(searchKey, [{ id: '1', type: 'TestObject' }])
759+
760+
const newObj = { id: '2', type: 'TestObject' }
761+
const count = await cache.invalidateByObject(newObj)
762+
763+
expect(count).toBe(1)
764+
const cached = await cache.get(searchKey)
765+
expect(cached).toBeNull()
766+
})
767+
768+
it('should invalidate searchPhrase caches', async () => {
769+
const searchKey = cache.generateKey('searchPhrase', { body: { type: 'TestObject' } })
770+
await cache.set(searchKey, [{ id: '1', type: 'TestObject' }])
771+
772+
const newObj = { id: '2', type: 'TestObject' }
773+
const count = await cache.invalidateByObject(newObj)
774+
775+
expect(count).toBe(1)
776+
const cached = await cache.get(searchKey)
777+
expect(cached).toBeNull()
778+
})
779+
780+
it('should not invalidate id, history, or since caches', async () => {
781+
// These caches should not be invalidated by object matching
782+
const idKey = cache.generateKey('id', '123')
783+
const historyKey = cache.generateKey('history', '123')
784+
const sinceKey = cache.generateKey('since', '2024-01-01')
785+
786+
await cache.set(idKey, { id: '123', type: 'TestObject' })
787+
await cache.set(historyKey, [{ id: '123' }])
788+
await cache.set(sinceKey, [{ id: '123' }])
789+
790+
const newObj = { id: '456', type: 'TestObject' }
791+
const count = await cache.invalidateByObject(newObj)
792+
793+
// None of these should be invalidated
794+
expect(await cache.get(idKey)).toBeTruthy()
795+
expect(await cache.get(historyKey)).toBeTruthy()
796+
expect(await cache.get(sinceKey)).toBeTruthy()
797+
})
798+
799+
it('should handle invalid input gracefully', async () => {
800+
expect(await cache.invalidateByObject(null)).toBe(0)
801+
expect(await cache.invalidateByObject(undefined)).toBe(0)
802+
expect(await cache.invalidateByObject('not an object')).toBe(0)
803+
expect(await cache.invalidateByObject(123)).toBe(0)
804+
})
805+
806+
it('should track invalidation count in stats', async () => {
807+
const queryKey = cache.generateKey('query', { body: { type: 'TestObject' } })
808+
await cache.set(queryKey, [{ id: '1' }])
809+
810+
const statsBefore = await cache.getStats()
811+
const invalidationsBefore = statsBefore.invalidations
812+
813+
await cache.invalidateByObject({ type: 'TestObject' })
814+
815+
const statsAfter = await cache.getStats()
816+
expect(statsAfter.invalidations).toBe(invalidationsBefore + 1)
817+
})
818+
})
819+
820+
describe('objectMatchesQuery', () => {
821+
it('should match simple property queries', () => {
822+
const obj = { type: 'TestObject', name: 'Test' }
823+
expect(cache.objectMatchesQuery(obj, { type: 'TestObject' })).toBe(true)
824+
expect(cache.objectMatchesQuery(obj, { type: 'OtherObject' })).toBe(false)
825+
})
826+
827+
it('should match queries with body property', () => {
828+
const obj = { type: 'TestObject' }
829+
expect(cache.objectMatchesQuery(obj, { body: { type: 'TestObject' } })).toBe(true)
830+
expect(cache.objectMatchesQuery(obj, { body: { type: 'OtherObject' } })).toBe(false)
831+
})
832+
833+
it('should match nested property queries', () => {
834+
const obj = { metadata: { author: 'John' } }
835+
expect(cache.objectMatchesQuery(obj, { 'metadata.author': 'John' })).toBe(true)
836+
expect(cache.objectMatchesQuery(obj, { 'metadata.author': 'Jane' })).toBe(false)
837+
})
838+
})
839+
840+
describe('objectContainsProperties', () => {
841+
it('should skip pagination parameters', () => {
842+
const obj = { type: 'TestObject' }
843+
expect(cache.objectContainsProperties(obj, { type: 'TestObject', limit: 10, skip: 5 })).toBe(true)
844+
})
845+
846+
it('should skip __rerum and _id properties', () => {
847+
const obj = { type: 'TestObject' }
848+
expect(cache.objectContainsProperties(obj, { type: 'TestObject', __rerum: {}, _id: '123' })).toBe(true)
849+
})
850+
851+
it('should match simple properties', () => {
852+
const obj = { type: 'TestObject', status: 'active' }
853+
expect(cache.objectContainsProperties(obj, { type: 'TestObject', status: 'active' })).toBe(true)
854+
expect(cache.objectContainsProperties(obj, { type: 'TestObject', status: 'inactive' })).toBe(false)
855+
})
856+
857+
it('should match nested objects', () => {
858+
const obj = { metadata: { author: 'John', year: 2024 } }
859+
expect(cache.objectContainsProperties(obj, { metadata: { author: 'John', year: 2024 } })).toBe(true)
860+
expect(cache.objectContainsProperties(obj, { metadata: { author: 'Jane' } })).toBe(false)
861+
})
862+
863+
it('should handle $exists operator', () => {
864+
const obj = { type: 'TestObject', optional: 'value' }
865+
expect(cache.objectContainsProperties(obj, { optional: { $exists: true } })).toBe(true)
866+
expect(cache.objectContainsProperties(obj, { missing: { $exists: false } })).toBe(true)
867+
expect(cache.objectContainsProperties(obj, { type: { $exists: false } })).toBe(false)
868+
})
869+
870+
it('should handle $ne operator', () => {
871+
const obj = { status: 'active' }
872+
expect(cache.objectContainsProperties(obj, { status: { $ne: 'inactive' } })).toBe(true)
873+
expect(cache.objectContainsProperties(obj, { status: { $ne: 'active' } })).toBe(false)
874+
})
875+
876+
it('should handle comparison operators', () => {
877+
const obj = { count: 42 }
878+
expect(cache.objectContainsProperties(obj, { count: { $gt: 40 } })).toBe(true)
879+
expect(cache.objectContainsProperties(obj, { count: { $gte: 42 } })).toBe(true)
880+
expect(cache.objectContainsProperties(obj, { count: { $lt: 50 } })).toBe(true)
881+
expect(cache.objectContainsProperties(obj, { count: { $lte: 42 } })).toBe(true)
882+
expect(cache.objectContainsProperties(obj, { count: { $gt: 50 } })).toBe(false)
883+
})
884+
885+
it('should handle $size operator for arrays', () => {
886+
const obj = { tags: ['a', 'b', 'c'] }
887+
expect(cache.objectContainsProperties(obj, { tags: { $size: 3 } })).toBe(true)
888+
expect(cache.objectContainsProperties(obj, { tags: { $size: 2 } })).toBe(false)
889+
})
890+
891+
it('should handle $or operator', () => {
892+
const obj = { type: 'TestObject' }
893+
expect(cache.objectContainsProperties(obj, {
894+
$or: [{ type: 'TestObject' }, { type: 'OtherObject' }]
895+
})).toBe(true)
896+
expect(cache.objectContainsProperties(obj, {
897+
$or: [{ type: 'Wrong1' }, { type: 'Wrong2' }]
898+
})).toBe(false)
899+
})
900+
901+
it('should handle $and operator', () => {
902+
const obj = { type: 'TestObject', status: 'active' }
903+
expect(cache.objectContainsProperties(obj, {
904+
$and: [{ type: 'TestObject' }, { status: 'active' }]
905+
})).toBe(true)
906+
expect(cache.objectContainsProperties(obj, {
907+
$and: [{ type: 'TestObject' }, { status: 'inactive' }]
908+
})).toBe(false)
909+
})
910+
})
911+
912+
describe('getNestedProperty', () => {
913+
it('should get top-level properties', () => {
914+
const obj = { name: 'Test' }
915+
expect(cache.getNestedProperty(obj, 'name')).toBe('Test')
916+
})
917+
918+
it('should get nested properties with dot notation', () => {
919+
const obj = {
920+
metadata: {
921+
author: {
922+
name: 'John'
923+
}
924+
}
925+
}
926+
expect(cache.getNestedProperty(obj, 'metadata.author.name')).toBe('John')
927+
})
928+
929+
it('should return undefined for missing properties', () => {
930+
const obj = { name: 'Test' }
931+
expect(cache.getNestedProperty(obj, 'missing')).toBeUndefined()
932+
expect(cache.getNestedProperty(obj, 'missing.nested')).toBeUndefined()
933+
})
934+
935+
it('should handle null/undefined gracefully', () => {
936+
const obj = { data: null }
937+
expect(cache.getNestedProperty(obj, 'data.nested')).toBeUndefined()
938+
})
939+
})
940+
941+
describe('evaluateFieldOperators', () => {
942+
it('should evaluate $exists correctly', () => {
943+
expect(cache.evaluateFieldOperators('value', { $exists: true })).toBe(true)
944+
expect(cache.evaluateFieldOperators(undefined, { $exists: false })).toBe(true)
945+
expect(cache.evaluateFieldOperators('value', { $exists: false })).toBe(false)
946+
})
947+
948+
it('should evaluate $size correctly', () => {
949+
expect(cache.evaluateFieldOperators([1, 2, 3], { $size: 3 })).toBe(true)
950+
expect(cache.evaluateFieldOperators([1, 2], { $size: 3 })).toBe(false)
951+
expect(cache.evaluateFieldOperators('not array', { $size: 1 })).toBe(false)
952+
})
953+
954+
it('should evaluate comparison operators correctly', () => {
955+
expect(cache.evaluateFieldOperators(10, { $gt: 5 })).toBe(true)
956+
expect(cache.evaluateFieldOperators(10, { $gte: 10 })).toBe(true)
957+
expect(cache.evaluateFieldOperators(10, { $lt: 20 })).toBe(true)
958+
expect(cache.evaluateFieldOperators(10, { $lte: 10 })).toBe(true)
959+
expect(cache.evaluateFieldOperators(10, { $ne: 5 })).toBe(true)
960+
})
961+
962+
it('should be conservative with unknown operators', () => {
963+
expect(cache.evaluateFieldOperators('value', { $unknown: 'test' })).toBe(true)
964+
})
965+
})
966+
967+
describe('evaluateOperator', () => {
968+
it('should evaluate $or correctly', () => {
969+
const obj = { type: 'A' }
970+
expect(cache.evaluateOperator(obj, '$or', [{ type: 'A' }, { type: 'B' }])).toBe(true)
971+
expect(cache.evaluateOperator(obj, '$or', [{ type: 'B' }, { type: 'C' }])).toBe(false)
972+
})
973+
974+
it('should evaluate $and correctly', () => {
975+
const obj = { type: 'A', status: 'active' }
976+
expect(cache.evaluateOperator(obj, '$and', [{ type: 'A' }, { status: 'active' }])).toBe(true)
977+
expect(cache.evaluateOperator(obj, '$and', [{ type: 'A' }, { status: 'inactive' }])).toBe(false)
978+
})
979+
980+
it('should be conservative with unknown operators', () => {
981+
const obj = { type: 'A' }
982+
expect(cache.evaluateOperator(obj, '$unknown', 'test')).toBe(true)
983+
})
984+
985+
it('should handle invalid input gracefully', () => {
986+
const obj = { type: 'A' }
987+
expect(cache.evaluateOperator(obj, '$or', 'not an array')).toBe(false)
988+
expect(cache.evaluateOperator(obj, '$and', 'not an array')).toBe(false)
989+
})
990+
})
991+
})

0 commit comments

Comments
 (0)