diff --git a/backend/package.json b/backend/package.json index ca4c47e8f..628339b8e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,6 +30,7 @@ "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-react": "^7.20.6", "jest": "^26.4.0", + "mockdate": "^3.0.5", "nodemon": "^2.0.2", "prettier": "^2.1.1", "pretty-quick": "^3.0.2", diff --git a/backend/workers/createRecurringEvents.js b/backend/workers/createRecurringEvents.js index 659b88c24..0516f1b12 100644 --- a/backend/workers/createRecurringEvents.js +++ b/backend/workers/createRecurringEvents.js @@ -1,141 +1,191 @@ const { generateEventData } = require('./lib/generateEventData'); -module.exports = (cron, fetch) => { - - // Check to see if any recurring events are happening today, - // and if so, check to see if an event has already been created - // for it. If not, create one. - - let EVENTS; - let RECURRING_EVENTS; - let TODAY_DATE; - let TODAY; - const URL = process.env.NODE_ENV === 'prod' ? 'https://www.vrms.io' : `http://localhost:${process.env.BACKEND_PORT}`; - - const headerToSend = process.env.CUSTOM_REQUEST_HEADER; - const fetchEvents = async () => { - try { - const res = await fetch(`${URL}/api/events/`, { - headers: { - "x-customrequired-header": headerToSend - } - }); - - EVENTS = await res.json(); - - // return EVENTS; - } catch(error) { - console.log(error); - }; - }; - - const fetchRecurringEvents = async () => { - try { - const res = await fetch(`${URL}/api/recurringevents/`, { - headers: { - "x-customrequired-header": headerToSend - } - }); - RECURRING_EVENTS = await res.json(); - - // return resJson; - } catch(error) { - console.log(error); - }; - }; - - async function filterAndCreateEvents() { - TODAY_DATE = new Date(); - TODAY = TODAY_DATE.getDay(); - console.log("Date: ", TODAY_DATE, "Day: ", TODAY); - const recurringEvents = RECURRING_EVENTS; - // console.log("Today Day: ", TODAY); - // Filter recurring events where the event date is today - if (recurringEvents && recurringEvents.length > 0) { - const filteredEvents = recurringEvents.filter(event => { - const eventDay = new Date(event.date).getDay(); - // console.log("Event Day: ", eventDay); - return (eventDay === TODAY); - }); - // For each recurring event, check to see if an event already - // exists for it and do something if true/false. Can't use - // forEach function with async/await. - for (filteredEvent of filteredEvents) { - const eventExists = await checkIfEventExists(filteredEvent.name); - - if (eventExists) { - //Do nothing - console.log("➖ Not going to run ceateEvent"); - } else { - // Create new event - const eventToCreate = generateEventData(filteredEvent); - - const created = await createEvent(eventToCreate); - console.log("➕", created); - }; - }; - }; - }; - - async function checkIfEventExists(eventName) { - const events = EVENTS; - // const today = new Date(); - - if (events && events.length > 0) { - const filteredEvents = events.filter(event => { - const eventDate = new Date(event.date); - const year = eventDate.getFullYear(); - const month = eventDate.getMonth(); - const date = eventDate.getDate(); - - const yearToday = TODAY_DATE.getFullYear(); - const monthToday = TODAY_DATE.getMonth(); - const dateToday = TODAY_DATE.getDate(); - - return (year === yearToday && month === monthToday && date === dateToday && eventName === event.name); - }); - console.log("Events already created: ", filteredEvents); - return filteredEvents.length > 0 ? true : false; - }; - }; - - const createEvent = async (event) => { - if(event) { - const jsonEvent = JSON.stringify(event); - const options = { - method: "POST", - headers: { - "Content-Type": "application/json", - "x-customrequired-header": headerToSend - }, - body: jsonEvent - } - - console.log('Running createEvent: ', jsonEvent); - - try { - const response = await fetch(`${URL}/api/events/`, options); - const resJson = await response.json(); - return resJson; - } catch (error) { - console.log(error); - }; - }; - }; - - async function runTask() { - console.log("Creating today's events"); - - await fetchEvents(); - await fetchRecurringEvents(); - await filterAndCreateEvents(); - - console.log("Today's events are created"); - - }; - - const scheduledTask = cron.schedule('*/30 * * * *', () => { - runTask(); +/** + * Utility to fetch data from an API endpoint. + * @param {string} endpoint - The API endpoint to fetch data from. + * @param {string} URL - The base URL for API requests. + * @param {string} headerToSend - Custom request header. + * @returns {Promise} - Resolves to the fetched data or an empty array on failure. + */ +const fetchData = async (endpoint, URL, headerToSend, fetch) => { + try { + const res = await fetch(`${URL}${endpoint}`, { + headers: { 'x-customrequired-header': headerToSend }, + }); + if (!res?.ok) throw new Error(`Failed to fetch: ${endpoint}`); + return await res.json(); + } catch (error) { + console.error(`Error fetching ${endpoint}:`, error); + return []; + } +}; + +/** + * Checks if two dates are on the same day in UTC. + * @param {Date} eventDate - Event date. + * @param {Date} todayDate - Today's data. + * @returns {boolean} - True if both dates are on the same UTC day. + */ +const isSameUTCDate = (eventDate, todayDate) => { + return ( + eventDate.getUTCFullYear() === todayDate.getUTCFullYear() && + eventDate.getUTCMonth() === todayDate.getUTCMonth() && + eventDate.getUTCDate() === todayDate.getUTCDate() + ); +}; + +/** + * Checks if an event with the given name already exists for today's date. + * @param {string} recurringEventName - The name of the recurring event to check. + * @param {Date} today - Today's date in UTC. + * @returns {boolean} - True if the event exists, false otherwise. + */ +const doesEventExist = (recurringEventName, today, events) => + events.some((event) => { + const eventDate = new Date(event.date); + return isSameUTCDate(eventDate, today) && event.name === recurringEventName; + }); + +/** + * Creates a new event by making a POST request to the events API. + * @param {Object} event - The event data to create. + * @returns {Promise} - The created event data or null on failure. + */ +const createEvent = async (event, URL, headerToSend, fetch) => { + if (!event) return null; + + try { + const res = await fetch(`${URL}/api/events/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-customrequired-header': headerToSend, + }, + body: JSON.stringify(event), }); - return scheduledTask; -}; \ No newline at end of file + if (!res.ok) throw new Error('Failed to create event'); + return await res.json(); + } catch (error) { + console.error('Error creating event:', error); + return null; + } +}; + +/** + * Filters recurring events happening today and creates new events if they do not already exist. + * Adjusts for Daylight Saving Time (DST) by converting stored UTC dates to Los Angeles time. + * @param {Array} events - The list of existing events. + * @param {Array} recurringEvents - The list of recurring events to check. + * @param {string} URL - The base URL for API requests. + * @param {string} headerToSend - Custom header for authentication or request tracking. + * @param {Function} fetch - Fetch function for making API calls. + * @returns {Promise} - A promise that resolves when all events are processed. + */ +const filterAndCreateEvents = async (events, recurringEvents, URL, headerToSend, fetch) => { + const today = new Date(); + const todayUTCDay = today.getUTCDay(); + // filter recurring events for today and not already existing + const eventsToCreate = recurringEvents.filter((recurringEvent) => { + // we're converting the stored UTC event date to local time to compare the system DOW with the event DOW + const localEventDate = adjustToLosAngelesTime(recurringEvent.date); + return ( + localEventDate.getUTCDay() === todayUTCDay && + !doesEventExist(recurringEvent.name, today, events) + ); + }); + + for (const event of eventsToCreate) { + // convert to local time for DST correction... + const correctedStartTime = adjustToLosAngelesTime(event.startTime); + const timeCorrectedEvent = { + ...event, + // ... then back to UTC for DB + date: correctedStartTime.toISOString(), + startTime: correctedStartTime.toISOString(), + }; + // map/generate all event data with adjusted date, startTime + const eventToCreate = generateEventData(timeCorrectedEvent); + + const createdEvent = await createEvent(eventToCreate, URL, headerToSend, fetch); + if (createdEvent) console.log('Created event:', createdEvent); + } +}; + +/** + * Adjusts an event date to Los_Angeles time, accounting for DST offsets. + * @param {Date} eventDate - The event date to adjust. + * @returns {Date} - The adjusted event date. + */ +const adjustToLosAngelesTime = (eventDate) => { + const tempDate = new Date(eventDate); + const losAngelesOffsetHours = new Intl.DateTimeFormat('en-US', { + timeZone: 'America/Los_Angeles', + timeZoneName: 'shortOffset', + }) + .formatToParts(tempDate) + .find((part) => part.type === 'timeZoneName') + .value.slice(3); + const offsetMinutes = parseInt(losAngelesOffsetHours, 10) * 60; + return new Date(tempDate.getTime() + offsetMinutes * 60000); +}; + +/** + * Executes the task of fetching existing events and recurring events, + * filtering those that should occur today, and creating them if needed. + * @param {Function} fetch - Fetch function for making API requests. + * @param {string} URL - The base URL for API requests. + * @param {string} headerToSend - Custom header for authentication or request tracking. + * @returns {Promise} - A promise that resolves when all tasks are completed. + */ +const runTask = async (fetch, URL, headerToSend) => { + console.log("Creating today's events..."); + const [events, recurringEvents] = await Promise.all([ + fetchData('/api/events/', URL, headerToSend, fetch), + fetchData('/api/recurringevents/', URL, headerToSend, fetch), + ]); + + await filterAndCreateEvents(events, recurringEvents, URL, headerToSend, fetch); + console.log("Today's events have been created."); +}; + +/** + * Schedules the runTask function to execute periodically using a cron job. + * @param {Object} cron - The cron scheduling library. + * @param {Function} fetch - Fetch function for making API requests. + * @param {string} URL - The base URL for API requests. + * @param {string} headerToSend - Custom header for authentication or request tracking. + * @returns {Object} - The scheduled cron job instance. + */ +const scheduleTask = (cron, fetch, URL, headerToSend) => { + return cron.schedule('*/30 * * * *', () => { + runTask(fetch, URL, headerToSend).catch((error) => console.error('Error running task:', error)); + }); +}; + +/** + * Wrapper function to initialize the worker with dependencies in app.js + * @param {Object} cron - The cron scheduling library. + * @param {Function} fetch - Fetch function for making API requests. + * @returns {Object} - The scheduled cron job instance. + */ +const createRecurringEvents = (cron, fetch) => { + const URL = + process.env.NODE_ENV === 'prod' + ? 'https://www.vrms.io' + : `http://localhost:${process.env.BACKEND_PORT}`; + const headerToSend = process.env.CUSTOM_REQUEST_HEADER; + + return scheduleTask(cron, fetch, URL, headerToSend); +}; + +module.exports = { + createRecurringEvents, + fetchData, + adjustToLosAngelesTime, + isSameUTCDate, + doesEventExist, + createEvent, + filterAndCreateEvents, + runTask, + scheduleTask, +}; diff --git a/backend/workers/createRecurringEvents.test.js b/backend/workers/createRecurringEvents.test.js new file mode 100644 index 000000000..56360ad1f --- /dev/null +++ b/backend/workers/createRecurringEvents.test.js @@ -0,0 +1,371 @@ +const { + fetchData, + adjustToLosAngelesTime, + isSameUTCDate, + doesEventExist, + createEvent, + filterAndCreateEvents, + runTask, + scheduleTask, +} = require('./createRecurringEvents'); +const { generateEventData } = require('./lib/generateEventData'); + +const MockDate = require('mockdate'); +const cron = require('node-cron'); + +jest.mock('./lib/generateEventData', () => ({ + generateEventData: jest.fn((event) => ({ + ...event, + generated: true, + })), +})); + +jest.mock('node-fetch', () => jest.fn()); +const fetch = require('node-fetch'); + +describe('createRecurringEvents Module Tests', () => { + const mockURL = 'http://localhost:3000'; + const mockHeader = 'mock-header'; + let mockEvents; + let mockRecurringEvents; + + beforeEach(() => { + MockDate.set('2023-11-02T00:00:00Z'); + + mockEvents = [ + { name: 'Event 1', date: '2023-11-02T19:00:00Z' }, + { name: 'Event 2', date: '2023-11-02T07:00:00Z' }, + ]; + mockRecurringEvents = [ + { name: 'Event 1', date: '2023-11-02T19:00:00Z' }, + { name: 'Event 2', date: '2023-11-02T07:00:00Z' }, + { name: 'Event 3', date: '2023-11-03T07:00:00Z' }, // Does not match today + ]; + + jest.clearAllMocks(); + }); + + afterEach(() => { + jest.clearAllMocks(); + MockDate.reset(); + }); + + describe('fetchData', () => { + it('should fetch data from the API endpoint', async () => { + fetch.mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValue(mockEvents), + }); + + const result = await fetchData('/api/events/', mockURL, mockHeader, fetch); + + expect(fetch).toHaveBeenCalledWith(`${mockURL}/api/events/`, { + headers: { 'x-customrequired-header': mockHeader }, + }); + expect(result).toEqual(mockEvents); + }); + + it('should handle API fetch failures', async () => { + fetch.mockRejectedValueOnce(new Error('Network error')); + + const result = await fetchData('/api/events/', mockURL, mockHeader, fetch); + + expect(fetch).toHaveBeenCalledTimes(1); + expect(result).toEqual([]); + }); + }); + + describe('adjustToLosAngelesTime', () => { + it('should correctly adjust timestamps before DST starts (PST -8)', () => { + const utcTimestamp = new Date('2024-03-10T07:00:00Z'); // 7 AM UTC + const expectedLocal = new Date('2024-03-09T23:00:00Z'); // 11 PM PST (-8) + + const result = adjustToLosAngelesTime(utcTimestamp); + + expect(result.toISOString()).toBe(expectedLocal.toISOString()); + }); + + it('should correctly adjust timestamps after DST starts (PDT -7)', () => { + const utcTimestamp = new Date('2024-03-11T07:00:00Z'); // 7 AM UTC (after DST) + const expectedLocal = new Date('2024-03-11T00:00:00Z'); // 12 AM PDT (-7) + + const result = adjustToLosAngelesTime(utcTimestamp); + + expect(result.toISOString()).toBe(expectedLocal.toISOString()); + }); + + it('should correctly adjust timestamps after DST ends (PST -8)', () => { + const utcTimestamp = new Date('2024-11-10T08:00:00Z'); // 8 AM UTC + const expectedLocal = new Date('2024-11-10T00:00:00Z'); // 12 AM PST (-8) + + const result = adjustToLosAngelesTime(utcTimestamp); + + expect(result.toISOString()).toBe(expectedLocal.toISOString()); + }); + + it('should correctly adjust timestamps when DST ends (PST -8)', () => { + const utcTimestamp = new Date('2024-11-03T09:00:00Z'); // 9 AM UTC + const expectedLocal = new Date('2024-11-03T01:00:00Z'); // 1 AM PST (UTC-8) + + const result = adjustToLosAngelesTime(utcTimestamp); + + expect(result.toISOString()).toBe(expectedLocal.toISOString()); + }); + + it('should correctly handle the repeated hour when DST ends (PST -8)', () => { + const utcTimestamp = new Date('2024-11-03T08:30:00Z'); // 8:30 AM UTC + const expectedLocal = new Date('2024-11-03T01:30:00Z'); // 1:30 AM PST (during repeat hour) + + const result = adjustToLosAngelesTime(utcTimestamp); + + expect(result.toISOString()).toBe(expectedLocal.toISOString()); + }); + }); + + describe('isSameUTCDate', () => { + it('should return true for the same UTC day', () => { + const date1 = new Date('2023-11-02T19:00:00Z'); + const date2 = new Date('2023-11-02T10:00:00Z'); + expect(isSameUTCDate(date1, date2)).toBe(true); + }); + + it('should return false for different UTC days', () => { + const date1 = new Date('2023-11-02T19:00:00Z'); + const date2 = new Date('2023-11-03T10:00:00Z'); + expect(isSameUTCDate(date1, date2)).toBe(false); + }); + }); + + describe('doesEventExist', () => { + it('should return true if an event exists on the same UTC day', () => { + const today = new Date('2023-11-02T00:00:00Z'); + expect(doesEventExist('Event 1', today, mockEvents)).toBe(true); + }); + + it('should return false if no event exists on the same UTC day', () => { + const today = new Date('2023-11-03T00:00:00Z'); + expect(doesEventExist('Event 1', today, mockEvents)).toBe(false); + }); + }); + + describe('filterAndCreateEvents', () => { + it('should not create events already present for today', async () => { + await filterAndCreateEvents(mockEvents, mockRecurringEvents, mockURL, mockHeader, fetch); + + expect(generateEventData).not.toHaveBeenCalledWith(mockRecurringEvents[0]); // Recurring Event 1 + expect(generateEventData).not.toHaveBeenCalledWith(mockRecurringEvents[1]); // Recurring Event 2 + expect(fetch).not.toHaveBeenCalled(); + }); + + it('should correctly adjust an event before DST ends (UTC-7 -> UTC-8)', async () => { + MockDate.set('2023-11-04T23:00:00Z'); // Before DST ends + + const preDstEvent = [ + { + name: 'Pre-DST Event', + date: '2023-11-04T08:00:00Z', // 8 AM UTC (1 AM PDT) + startTime: '2023-11-04T08:00:00Z', + // hours: 1, + }, + ]; + await filterAndCreateEvents([], preDstEvent, mockURL, mockHeader, fetch); + + expect(generateEventData).toHaveBeenCalledWith( + expect.objectContaining({ name: 'Pre-DST Event' }), + ); + + const expectedEvent = { + name: 'Pre-DST Event', + date: new Date('2023-11-04T01:00:00Z').toISOString(), // Should match 1 AM PDT + startTime: new Date('2023-11-04T01:00:00Z').toISOString(), + generated: true, + }; + + expect(fetch).toHaveBeenCalledWith( + `${mockURL}/api/events/`, + expect.objectContaining({ + body: JSON.stringify(expectedEvent), + }), + ); + + MockDate.reset(); + }); + + it('should correctly adjust an event during DST ending (PDT -> PST shift)', async () => { + MockDate.set('2023-11-05T02:00:00Z'); // The moment of DST shift + + const dstTransitionEvent = [ + { + name: 'DST Shift Event', + date: '2023-11-05T09:00:00Z', + startTime: '2023-11-05T09:00:00Z', + }, + ]; + + await filterAndCreateEvents([], dstTransitionEvent, mockURL, mockHeader, fetch); + + expect(generateEventData).toHaveBeenCalledWith( + expect.objectContaining({ name: 'DST Shift Event' }), + ); + const expectedEvent = { + name: 'DST Shift Event', + date: new Date('2023-11-05T01:00:00Z').toISOString(), + startTime: new Date('2023-11-05T01:00:00Z').toISOString(), + generated: true, + }; + + expect(fetch).toHaveBeenCalledWith( + `${mockURL}/api/events/`, + expect.objectContaining({ + body: JSON.stringify(expectedEvent), + }), + ); + + MockDate.reset(); + }); + + it('should correctly adjust an event before DST starts (UTC-8 -> UTC-7)', async () => { + MockDate.set('2024-03-10T09:00:00Z'); // 1 AM PST before the shift + + const preDstStartEvent = [ + { + name: 'Pre-DST Start Event', + date: '2024-03-10T09:00:00Z', // 1 AM PST in UTC-8 + startTime: '2024-03-10T09:00:00Z', + }, + ]; + + await filterAndCreateEvents([], preDstStartEvent, mockURL, mockHeader, fetch); + + expect(generateEventData).toHaveBeenCalledWith( + expect.objectContaining({ name: 'Pre-DST Start Event' }), + ); + + const expectedEvent = { + name: 'Pre-DST Start Event', + date: new Date('2024-03-10T01:00:00Z').toISOString(), // Should match 1 AM PST + startTime: new Date('2024-03-10T01:00:00Z').toISOString(), + generated: true, + }; + + expect(fetch).toHaveBeenCalledWith( + `${mockURL}/api/events/`, + expect.objectContaining({ + body: JSON.stringify(expectedEvent), + }), + ); + + MockDate.reset(); + }); + + it('should correctly adjust an event during DST start (PST -> PDT shift)', async () => { + MockDate.set('2024-03-10T10:00:00Z'); + + const dstStartTransitionEvent = [ + { + name: 'DST Start Event', + date: '2024-03-10T10:00:00Z', // 2 AM PST in UTC-8 + startTime: '2024-03-10T10:00:00Z', + }, + ]; + await filterAndCreateEvents([], dstStartTransitionEvent, mockURL, mockHeader, fetch); + + expect(generateEventData).toHaveBeenCalledWith( + expect.objectContaining({ name: 'DST Start Event' }), + ); + + const expectedEvent = { + name: 'DST Start Event', + date: new Date('2024-03-10T03:00:00Z').toISOString(), // Should match 3 AM PDT + startTime: new Date('2024-03-10T03:00:00Z').toISOString(), + generated: true, + }; + + expect(fetch).toHaveBeenCalledWith( + `${mockURL}/api/events/`, + expect.objectContaining({ + body: JSON.stringify(expectedEvent), + }), + ); + + MockDate.reset(); + }); + }); + + describe('runTask', () => { + it('should fetch data but not create events if all exist', async () => { + // First API call response (events) + fetch.mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValue(mockEvents), + }); + + // Second API call response (recurring events) + fetch.mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValue(mockRecurringEvents), + }); + + await runTask(fetch, mockURL, mockHeader); + + console.log('Actual fetch calls:', fetch.mock.calls); + // Expect only 2 fetch calls (no event creation needed) + expect(fetch).toHaveBeenCalledTimes(2); + + expect(fetch).toHaveBeenCalledWith( + `${mockURL}/api/recurringevents/`, + expect.objectContaining({ headers: { 'x-customrequired-header': mockHeader } }), + ); + + // Ensure no call to createEvent + expect(fetch).not.toHaveBeenCalledWith( + `${mockURL}/api/events/`, + expect.objectContaining({ method: 'POST' }), + ); + }); + }); + + describe('createEvent', () => { + it('should create a new event via POST request', async () => { + const mockEvent = { name: 'Event 1', date: '2023-11-02T19:00:00Z' }; + fetch.mockResolvedValueOnce({ + ok: true, + json: jest.fn().mockResolvedValue({ id: 1, ...mockEvent }), + }); + + const result = await createEvent(mockEvent, mockURL, mockHeader, fetch); + + expect(fetch).toHaveBeenCalledWith(`${mockURL}/api/events/`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'x-customrequired-header': mockHeader, + }, + body: JSON.stringify(mockEvent), + }); + expect(result).toEqual({ id: 1, ...mockEvent }); + }); + + it('should return null if event creation fails', async () => { + fetch.mockRejectedValueOnce(new Error('Network error')); + + const result = await createEvent(null, mockURL, mockHeader, fetch); + + expect(result).toBeNull(); + }); + }); + + describe('scheduleTask', () => { + it('should schedule the runTask function', () => { + const scheduleSpy = jest.spyOn(cron, 'schedule').mockImplementation((_, callback) => { + callback(); + }); + + scheduleTask(cron, fetch, mockURL, mockHeader); + + expect(scheduleSpy).toHaveBeenCalledWith('*/30 * * * *', expect.any(Function)); + + scheduleSpy.mockRestore(); + }); + }); +}); diff --git a/backend/yarn.lock b/backend/yarn.lock index eacbe55ef..26b84d334 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -4818,6 +4818,11 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mockdate@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-3.0.5.tgz#789be686deb3149e7df2b663d2bc4392bc3284fb" + integrity sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ== + mongodb-memory-server-core@6.6.7: version "6.6.7" resolved "https://registry.yarnpkg.com/mongodb-memory-server-core/-/mongodb-memory-server-core-6.6.7.tgz#f402bb5808f052e20d040cd9ce03a64dde94fc62"