Skip to content

Commit a8d368c

Browse files
committed
gog routes too
1 parent a6e60c3 commit a8d368c

4 files changed

Lines changed: 283 additions & 2 deletions

File tree

cache/cache.test.js

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
cacheId,
1313
cacheHistory,
1414
cacheSince,
15+
cacheGogFragments,
16+
cacheGogGlosses,
1517
cacheStats,
1618
cacheClear
1719
} from './middleware.js'
@@ -471,3 +473,194 @@ describe('Cache Statistics', () => {
471473
expect(cache.cache.size).toBe(1)
472474
})
473475
})
476+
477+
describe('GOG Endpoint Cache Middleware', () => {
478+
let mockReq
479+
let mockRes
480+
let mockNext
481+
482+
beforeEach(() => {
483+
// Clear cache before each test
484+
cache.clear()
485+
486+
// Reset mock request
487+
mockReq = {
488+
method: 'POST',
489+
body: {},
490+
query: {},
491+
params: {}
492+
}
493+
494+
// Reset mock response
495+
mockRes = {
496+
statusCode: 200,
497+
headers: {},
498+
set: jest.fn(function(key, value) {
499+
if (typeof key === 'object') {
500+
Object.assign(this.headers, key)
501+
} else {
502+
this.headers[key] = value
503+
}
504+
return this
505+
}),
506+
json: jest.fn(function(data) {
507+
this.jsonData = data
508+
return this
509+
})
510+
}
511+
512+
// Reset mock next
513+
mockNext = jest.fn()
514+
})
515+
516+
afterEach(() => {
517+
cache.clear()
518+
})
519+
520+
describe('cacheGogFragments middleware', () => {
521+
it('should pass through when ManuscriptWitness is missing', () => {
522+
mockReq.body = {}
523+
524+
cacheGogFragments(mockReq, mockRes, mockNext)
525+
526+
expect(mockNext).toHaveBeenCalled()
527+
expect(mockRes.json).not.toHaveBeenCalled()
528+
})
529+
530+
it('should pass through when ManuscriptWitness is invalid', () => {
531+
mockReq.body = { ManuscriptWitness: 'not-a-url' }
532+
533+
cacheGogFragments(mockReq, mockRes, mockNext)
534+
535+
expect(mockNext).toHaveBeenCalled()
536+
expect(mockRes.json).not.toHaveBeenCalled()
537+
})
538+
539+
it('should return cache MISS on first request', () => {
540+
mockReq.body = { ManuscriptWitness: 'https://example.org/manuscript/1' }
541+
mockReq.query = { limit: '50', skip: '0' }
542+
543+
cacheGogFragments(mockReq, mockRes, mockNext)
544+
545+
expect(mockRes.headers['X-Cache']).toBe('MISS')
546+
expect(mockNext).toHaveBeenCalled()
547+
})
548+
549+
it('should return cache HIT on second identical request', () => {
550+
mockReq.body = { ManuscriptWitness: 'https://example.org/manuscript/1' }
551+
mockReq.query = { limit: '50', skip: '0' }
552+
553+
// First request - populate cache
554+
cacheGogFragments(mockReq, mockRes, mockNext)
555+
mockRes.json([{ '@id': 'fragment1', '@type': 'WitnessFragment' }])
556+
557+
// Reset mocks for second request
558+
mockRes.headers = {}
559+
mockRes.json = jest.fn()
560+
mockNext = jest.fn()
561+
562+
// Second request - should hit cache
563+
cacheGogFragments(mockReq, mockRes, mockNext)
564+
565+
expect(mockRes.headers['X-Cache']).toBe('HIT')
566+
expect(mockRes.json).toHaveBeenCalledWith([{ '@id': 'fragment1', '@type': 'WitnessFragment' }])
567+
expect(mockNext).not.toHaveBeenCalled()
568+
})
569+
570+
it('should cache based on pagination parameters', () => {
571+
const manuscriptURI = 'https://example.org/manuscript/1'
572+
573+
// Request with limit=50, skip=0
574+
mockReq.body = { ManuscriptWitness: manuscriptURI }
575+
mockReq.query = { limit: '50', skip: '0' }
576+
577+
cacheGogFragments(mockReq, mockRes, mockNext)
578+
mockRes.json([{ '@id': 'fragment1' }])
579+
580+
// Request with different pagination - should be MISS
581+
mockRes.headers = {}
582+
mockRes.json = jest.fn()
583+
mockNext = jest.fn()
584+
mockReq.query = { limit: '100', skip: '0' }
585+
586+
cacheGogFragments(mockReq, mockRes, mockNext)
587+
588+
expect(mockRes.headers['X-Cache']).toBe('MISS')
589+
expect(mockNext).toHaveBeenCalled()
590+
})
591+
})
592+
593+
describe('cacheGogGlosses middleware', () => {
594+
it('should pass through when ManuscriptWitness is missing', () => {
595+
mockReq.body = {}
596+
597+
cacheGogGlosses(mockReq, mockRes, mockNext)
598+
599+
expect(mockNext).toHaveBeenCalled()
600+
expect(mockRes.json).not.toHaveBeenCalled()
601+
})
602+
603+
it('should pass through when ManuscriptWitness is invalid', () => {
604+
mockReq.body = { ManuscriptWitness: 'not-a-url' }
605+
606+
cacheGogGlosses(mockReq, mockRes, mockNext)
607+
608+
expect(mockNext).toHaveBeenCalled()
609+
expect(mockRes.json).not.toHaveBeenCalled()
610+
})
611+
612+
it('should return cache MISS on first request', () => {
613+
mockReq.body = { ManuscriptWitness: 'https://example.org/manuscript/1' }
614+
mockReq.query = { limit: '50', skip: '0' }
615+
616+
cacheGogGlosses(mockReq, mockRes, mockNext)
617+
618+
expect(mockRes.headers['X-Cache']).toBe('MISS')
619+
expect(mockNext).toHaveBeenCalled()
620+
})
621+
622+
it('should return cache HIT on second identical request', () => {
623+
mockReq.body = { ManuscriptWitness: 'https://example.org/manuscript/1' }
624+
mockReq.query = { limit: '50', skip: '0' }
625+
626+
// First request - populate cache
627+
cacheGogGlosses(mockReq, mockRes, mockNext)
628+
mockRes.json([{ '@id': 'gloss1', '@type': 'Gloss' }])
629+
630+
// Reset mocks for second request
631+
mockRes.headers = {}
632+
mockRes.json = jest.fn()
633+
mockNext = jest.fn()
634+
635+
// Second request - should hit cache
636+
cacheGogGlosses(mockReq, mockRes, mockNext)
637+
638+
expect(mockRes.headers['X-Cache']).toBe('HIT')
639+
expect(mockRes.json).toHaveBeenCalledWith([{ '@id': 'gloss1', '@type': 'Gloss' }])
640+
expect(mockNext).not.toHaveBeenCalled()
641+
})
642+
643+
it('should cache based on pagination parameters', () => {
644+
const manuscriptURI = 'https://example.org/manuscript/1'
645+
646+
// Request with limit=50, skip=0
647+
mockReq.body = { ManuscriptWitness: manuscriptURI }
648+
mockReq.query = { limit: '50', skip: '0' }
649+
650+
cacheGogGlosses(mockReq, mockRes, mockNext)
651+
mockRes.json([{ '@id': 'gloss1' }])
652+
653+
// Request with different pagination - should be MISS
654+
mockRes.headers = {}
655+
mockRes.json = jest.fn()
656+
mockNext = jest.fn()
657+
mockReq.query = { limit: '100', skip: '0' }
658+
659+
cacheGogGlosses(mockReq, mockRes, mockNext)
660+
661+
expect(mockRes.headers['X-Cache']).toBe('MISS')
662+
expect(mockNext).toHaveBeenCalled()
663+
})
664+
})
665+
})
666+

cache/middleware.js

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,13 +355,99 @@ const cacheClear = (req, res) => {
355355
})
356356
}
357357

358+
/**
359+
* Cache middleware for GOG fragments endpoint
360+
* Caches POST requests for WitnessFragment entities from ManuscriptWitness
361+
* Cache key includes ManuscriptWitness URI and pagination parameters
362+
*/
363+
const cacheGogFragments = (req, res, next) => {
364+
// Only cache if request has valid body with ManuscriptWitness
365+
const manID = req.body?.["ManuscriptWitness"]
366+
if (!manID || !manID.startsWith("http")) {
367+
return next()
368+
}
369+
370+
const limit = parseInt(req.query.limit ?? 50)
371+
const skip = parseInt(req.query.skip ?? 0)
372+
373+
// Generate cache key from ManuscriptWitness URI and pagination
374+
const cacheKey = `gog-fragments:${manID}:limit=${limit}:skip=${skip}`
375+
376+
const cachedResponse = cache.get(cacheKey)
377+
if (cachedResponse) {
378+
console.log(`Cache HIT for GOG fragments: ${manID}`)
379+
res.set('X-Cache', 'HIT')
380+
res.set('Content-Type', 'application/json; charset=utf-8')
381+
res.json(cachedResponse)
382+
return
383+
}
384+
385+
console.log(`Cache MISS for GOG fragments: ${manID}`)
386+
res.set('X-Cache', 'MISS')
387+
388+
// Intercept res.json to cache the response
389+
const originalJson = res.json.bind(res)
390+
res.json = (data) => {
391+
if (res.statusCode === 200 && Array.isArray(data)) {
392+
cache.set(cacheKey, data)
393+
}
394+
return originalJson(data)
395+
}
396+
397+
next()
398+
}
399+
400+
/**
401+
* Cache middleware for GOG glosses endpoint
402+
* Caches POST requests for Gloss entities from ManuscriptWitness
403+
* Cache key includes ManuscriptWitness URI and pagination parameters
404+
*/
405+
const cacheGogGlosses = (req, res, next) => {
406+
// Only cache if request has valid body with ManuscriptWitness
407+
const manID = req.body?.["ManuscriptWitness"]
408+
if (!manID || !manID.startsWith("http")) {
409+
return next()
410+
}
411+
412+
const limit = parseInt(req.query.limit ?? 50)
413+
const skip = parseInt(req.query.skip ?? 0)
414+
415+
// Generate cache key from ManuscriptWitness URI and pagination
416+
const cacheKey = `gog-glosses:${manID}:limit=${limit}:skip=${skip}`
417+
418+
const cachedResponse = cache.get(cacheKey)
419+
if (cachedResponse) {
420+
console.log(`Cache HIT for GOG glosses: ${manID}`)
421+
res.set('X-Cache', 'HIT')
422+
res.set('Content-Type', 'application/json; charset=utf-8')
423+
res.json(cachedResponse)
424+
return
425+
}
426+
427+
console.log(`Cache MISS for GOG glosses: ${manID}`)
428+
res.set('X-Cache', 'MISS')
429+
430+
// Intercept res.json to cache the response
431+
const originalJson = res.json.bind(res)
432+
res.json = (data) => {
433+
if (res.statusCode === 200 && Array.isArray(data)) {
434+
cache.set(cacheKey, data)
435+
}
436+
return originalJson(data)
437+
}
438+
439+
next()
440+
}
441+
358442
export {
359443
cacheQuery,
360444
cacheSearch,
361445
cacheSearchPhrase,
362446
cacheId,
363447
cacheHistory,
364448
cacheSince,
449+
cacheGogFragments,
450+
cacheGogGlosses,
365451
invalidateCache,
366452
cacheStats,
367453
cacheClear

routes/_gog_fragments_from_manuscript.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ const router = express.Router()
33
//This controller will handle all MongoDB interactions.
44
import controller from '../db-controller.js'
55
import auth from '../auth/index.js'
6+
import { cacheGogFragments } from '../cache/middleware.js'
67

78
router.route('/')
8-
.post(auth.checkJwt, controller._gog_fragments_from_manuscript)
9+
.post(auth.checkJwt, cacheGogFragments, controller._gog_fragments_from_manuscript)
910
.all((req, res, next) => {
1011
res.statusMessage = 'Improper request method. Please use POST.'
1112
res.status(405)

routes/_gog_glosses_from_manuscript.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ const router = express.Router()
33
//This controller will handle all MongoDB interactions.
44
import controller from '../db-controller.js'
55
import auth from '../auth/index.js'
6+
import { cacheGogGlosses } from '../cache/middleware.js'
67

78
router.route('/')
8-
.post(auth.checkJwt, controller._gog_glosses_from_manuscript)
9+
.post(auth.checkJwt, cacheGogGlosses, controller._gog_glosses_from_manuscript)
910
.all((req, res, next) => {
1011
res.statusMessage = 'Improper request method. Please use POST.'
1112
res.status(405)

0 commit comments

Comments
 (0)