-
Notifications
You must be signed in to change notification settings - Fork 64
Add findByFields method #50
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
Changes from all commits
0106bb8
c3459a1
6be5fbb
dd75571
e8f12f9
92d6ee9
3a7d5e5
bc4533c
75ad46f
2a6e365
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 |
---|---|---|
|
@@ -9,19 +9,36 @@ const hexId = 'aaaa0000bbbb0000cccc0000' | |
|
||
const docs = { | ||
one: { | ||
_id: ObjectId(hexId) | ||
_id: ObjectId(hexId), | ||
foo: 'bar', | ||
tags: ['foo', 'bar'] | ||
}, | ||
two: { | ||
_id: ObjectId() | ||
_id: ObjectId(), | ||
foo: 'bar' | ||
} | ||
} | ||
|
||
const stringDoc = { | ||
_id: 's2QBCnv6fXv5YbjAP' | ||
_id: 's2QBCnv6fXv5YbjAP', | ||
tags: ['bar', 'baz'] | ||
} | ||
|
||
const collectionName = 'test' | ||
const cacheKey = id => `mongo-${collectionName}-${idToString(id)}` | ||
const cacheKeyById = id => `mongo-${collectionName}-${idToString(id)}` | ||
const cacheKeyByFields = fields => { | ||
const cleanedFields = {} | ||
|
||
Object.keys(fields).forEach(key => { | ||
if (typeof key !== 'undefined') { | ||
cleanedFields[key] = Array.isArray(fields[key]) | ||
? fields[key] | ||
: [fields[key]] | ||
} | ||
}) | ||
|
||
return `mongo-${collectionName}-${JSON.stringify(cleanedFields)}` | ||
} | ||
|
||
describe('createCachingMethods', () => { | ||
let collection | ||
|
@@ -31,30 +48,55 @@ describe('createCachingMethods', () => { | |
beforeEach(() => { | ||
collection = { | ||
collectionName, | ||
find: jest.fn(({ _id: { $in: ids } }) => ({ | ||
toArray: () => | ||
new Promise(resolve => { | ||
setTimeout( | ||
() => | ||
resolve( | ||
ids.map(id => { | ||
if (id === stringDoc._id) { | ||
return stringDoc | ||
} | ||
|
||
if (id.equals(docs.one._id)) { | ||
return docs.one | ||
} | ||
|
||
if (id.equals(docs.two._id)) { | ||
return docs.two | ||
} | ||
}) | ||
), | ||
0 | ||
) | ||
}) | ||
})) | ||
find: jest.fn(filter => { | ||
return { | ||
toArray: () => | ||
new Promise(resolve => { | ||
setTimeout( | ||
() => | ||
resolve( | ||
[docs.one, docs.two, stringDoc].filter(doc => { | ||
for (const orFilter of filter.$or || [filter]) { | ||
for (const field in orFilter) { | ||
if (field === '_id') { | ||
for (const id of orFilter._id.$in) { | ||
if (id.equals && !id.equals(doc._id)) { | ||
break | ||
} else if ( | ||
doc._id.equals && | ||
!doc._id.equals(id) | ||
) { | ||
break | ||
} else if ( | ||
!id.equals && | ||
!doc._id.equals && | ||
id !== doc._id | ||
) { | ||
break | ||
} | ||
} | ||
} else if (Array.isArray(doc[field])) { | ||
for (const value of orFilter[field].$in) { | ||
if (!doc[field].includes(value)) { | ||
break | ||
} | ||
} | ||
} else if ( | ||
!orFilter[field].$in.includes(doc[field]) | ||
) { | ||
break | ||
} | ||
} | ||
return true | ||
} | ||
return false | ||
}) | ||
), | ||
0 | ||
) | ||
}) | ||
} | ||
}) | ||
} | ||
|
||
cache = new InMemoryLRUCache() | ||
|
@@ -65,10 +107,11 @@ describe('createCachingMethods', () => { | |
it('adds the right methods', () => { | ||
expect(api.findOneById).toBeDefined() | ||
expect(api.findManyByIds).toBeDefined() | ||
expect(api.findByFields).toBeDefined() | ||
expect(api.deleteFromCacheById).toBeDefined() | ||
}) | ||
|
||
it('finds one', async () => { | ||
it('finds one with ObjectId', async () => { | ||
const doc = await api.findOneById(docs.one._id) | ||
expect(doc).toBe(docs.one) | ||
expect(collection.find.mock.calls.length).toBe(1) | ||
|
@@ -89,48 +132,114 @@ describe('createCachingMethods', () => { | |
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
// TODO why doesn't this pass? | ||
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. I'd like this to remain in the file until solved 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.
|
||
// it.only(`doesn't cache without ttl`, async () => { | ||
// await api.findOneById(docs.id1._id) | ||
// await api.findOneById(docs.id1._id) | ||
it('finds by field', async () => { | ||
const foundDocs = await api.findByFields({ foo: 'bar' }) | ||
|
||
expect(foundDocs[0]).toBe(docs.one) | ||
expect(foundDocs[1]).toBe(docs.two) | ||
expect(foundDocs.length).toBe(2) | ||
|
||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it('finds by array field', async () => { | ||
const foundDocs = await api.findByFields({ tags: 'bar' }) | ||
|
||
expect(foundDocs[0]).toBe(docs.one) | ||
expect(foundDocs[1]).toBe(stringDoc) | ||
expect(foundDocs.length).toBe(2) | ||
|
||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it('finds by mutiple fields', async () => { | ||
const foundDocs = await api.findByFields({ | ||
tags: ['foo', 'bar'], | ||
foo: 'bar' | ||
}) | ||
|
||
expect(foundDocs[0]).toBe(docs.one) | ||
expect(foundDocs.length).toBe(1) | ||
|
||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it(`doesn't mix filters of pending calls for different fields`, async () => { | ||
const pendingDocs1 = api.findByFields({ foo: 'bar' }) | ||
const pendingDocs2 = api.findByFields({ tags: 'baz' }) | ||
const [foundDocs1, foundDocs2] = await Promise.all([ | ||
pendingDocs1, | ||
pendingDocs2 | ||
]) | ||
|
||
expect(foundDocs1[0]).toBe(docs.one) | ||
expect(foundDocs1[1]).toBe(docs.two) | ||
expect(foundDocs1.length).toBe(2) | ||
expect(foundDocs2[0]).toBe(stringDoc) | ||
expect(foundDocs2.length).toBe(1) | ||
|
||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it(`dataloader caches each value individually when finding by a single field`, async () => { | ||
await api.findByFields({ tags: ['foo', 'baz'] }, { ttl: 1 }) | ||
|
||
const fooBazCacheValue = await cache.get( | ||
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.
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. This is testing the memoization caching. The desired behavior is that if |
||
cacheKeyByFields({ tags: ['foo', 'baz'] }) | ||
) | ||
const fooCacheValue = await cache.get(cacheKeyByFields({ tags: 'foo' })) | ||
|
||
expect(fooBazCacheValue).toBeDefined() | ||
expect(fooCacheValue).toBeUndefined() | ||
|
||
// expect(collection.find.mock.calls.length).toBe(2) | ||
// }) | ||
await api.findByFields({ tags: 'foo' }) | ||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it(`doesn't cache without ttl`, async () => { | ||
await api.findOneById(docs.one._id) | ||
|
||
const value = await cache.get(cacheKey(docs.one._id)) | ||
const value = await cache.get(cacheKeyById(docs.one._id)) | ||
expect(value).toBeUndefined() | ||
}) | ||
|
||
it(`caches`, async () => { | ||
await api.findOneById(docs.one._id, { ttl: 1 }) | ||
const value = await cache.get(cacheKey(docs.one._id)) | ||
const value = await cache.get(cacheKeyById(docs.one._id)) | ||
expect(value).toEqual(EJSON.stringify(docs.one)) | ||
|
||
await api.findOneById(docs.one._id) | ||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it(`caches non-array field values as arrays`, async () => { | ||
const fields = { tags: 'foo' } | ||
await api.findByFields(fields, { ttl: 1 }) | ||
const value = await cache.get(cacheKeyByFields(fields)) | ||
expect(value).toEqual(EJSON.stringify([docs.one])) | ||
|
||
await api.findByFields({ tags: ['foo'] }) | ||
expect(collection.find.mock.calls.length).toBe(1) | ||
}) | ||
|
||
it(`caches with ttl`, async () => { | ||
await api.findOneById(docs.one._id, { ttl: 1 }) | ||
await wait(1001) | ||
|
||
const value = await cache.get(cacheKey(docs.one._id)) | ||
const value = await cache.get(cacheKeyById(docs.one._id)) | ||
expect(value).toBeUndefined() | ||
}) | ||
|
||
it(`deletes from cache`, async () => { | ||
for (const doc of [docs.one, docs.two, stringDoc]) { | ||
await api.findOneById(doc._id, { ttl: 1 }) | ||
|
||
const valueBefore = await cache.get(cacheKey(doc._id)) | ||
const valueBefore = await cache.get(cacheKeyById(doc._id)) | ||
expect(valueBefore).toEqual(EJSON.stringify(doc)) | ||
|
||
await api.deleteFromCacheById(doc._id) | ||
|
||
const valueAfter = await cache.get(cacheKey(doc._id)) | ||
const valueAfter = await cache.get(cacheKeyById(doc._id)) | ||
expect(valueAfter).toBeUndefined() | ||
} | ||
}) | ||
|
Uh oh!
There was an error while loading. Please reload this page.