From 19336ce6ec3560fc017a5b978aa5d2a849726232 Mon Sep 17 00:00:00 2001 From: Brendan Chen Date: Wed, 2 Jul 2025 19:47:44 -0400 Subject: [PATCH] Add setLoggingInterval method and implementation for tests and other purposes. Let users control the logging interval of both parking repositories. --- src/repositories/InMemoryParkingRepository.ts | 11 ++- .../ParkingGetterSetterRepository.ts | 2 + src/repositories/RedisParkingRepository.ts | 9 ++- .../ParkingRepositorySharedTests.test.ts | 77 ++++++++++--------- 4 files changed, 57 insertions(+), 42 deletions(-) diff --git a/src/repositories/InMemoryParkingRepository.ts b/src/repositories/InMemoryParkingRepository.ts index e162dcb..05c7b3e 100644 --- a/src/repositories/InMemoryParkingRepository.ts +++ b/src/repositories/InMemoryParkingRepository.ts @@ -17,6 +17,7 @@ export const MAX_NUM_ENTRIES = 2016; export class InMemoryParkingRepository implements ParkingGetterSetterRepository { private dataLastAdded: Map = new Map(); + private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS; constructor( private structures: Map = new Map(), @@ -33,9 +34,9 @@ export class InMemoryParkingRepository implements ParkingGetterSetterRepository const now = Date.now(); const lastAdded = this.dataLastAdded.get(structure.id); - function parkingLoggingIntervalExceeded() { - return !lastAdded || (now - lastAdded.getTime()) >= PARKING_LOGGING_INTERVAL_MS; - } + const parkingLoggingIntervalExceeded = () => { + return !lastAdded || (now - lastAdded.getTime()) >= this.loggingIntervalMs; + }; if (parkingLoggingIntervalExceeded()) { const timestampRecord: IParkingStructureTimestampRecord = { @@ -131,4 +132,8 @@ export class InMemoryParkingRepository implements ParkingGetterSetterRepository averageSpotsAvailable }; }; + + setLoggingInterval = (intervalMs: number): void => { + this.loggingIntervalMs = intervalMs; + }; } diff --git a/src/repositories/ParkingGetterSetterRepository.ts b/src/repositories/ParkingGetterSetterRepository.ts index cfe23ba..63b60e5 100644 --- a/src/repositories/ParkingGetterSetterRepository.ts +++ b/src/repositories/ParkingGetterSetterRepository.ts @@ -7,4 +7,6 @@ export interface ParkingGetterSetterRepository extends ParkingGetterRepository { removeParkingStructureIfExists(id: string): Promise; clearParkingStructureData(): Promise; + + setLoggingInterval(intervalMs: number): void; } diff --git a/src/repositories/RedisParkingRepository.ts b/src/repositories/RedisParkingRepository.ts index 07fa188..99be870 100644 --- a/src/repositories/RedisParkingRepository.ts +++ b/src/repositories/RedisParkingRepository.ts @@ -1,4 +1,4 @@ -import { createClient, RedisClientType } from 'redis'; +import { createClient } from 'redis'; import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository"; import { IParkingStructure, IParkingStructureTimestampRecord } from "../entities/ParkingRepositoryEntities"; import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository"; @@ -10,6 +10,7 @@ export const PARKING_LOGGING_INTERVAL_MS = 600000; export class RedisParkingRepository implements ParkingGetterSetterRepository { private dataLastAdded: Map = new Map(); + private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS; constructor( private redisClient = createClient({ @@ -61,7 +62,7 @@ export class RedisParkingRepository implements ParkingGetterSetterRepository { const lastAdded = this.dataLastAdded.get(structure.id); const parkingLoggingIntervalExceeded = () => { - return !lastAdded || (now - lastAdded.getTime()) >= PARKING_LOGGING_INTERVAL_MS; + return !lastAdded || (now - lastAdded.getTime()) >= this.loggingIntervalMs; }; if (parkingLoggingIntervalExceeded()) { @@ -262,4 +263,8 @@ export class RedisParkingRepository implements ParkingGetterSetterRepository { averageSpotsAvailable }; }; + + setLoggingInterval = (intervalMs: number): void => { + this.loggingIntervalMs = intervalMs; + }; } diff --git a/test/repositories/ParkingRepositorySharedTests.test.ts b/test/repositories/ParkingRepositorySharedTests.test.ts index 319008c..58c6880 100644 --- a/test/repositories/ParkingRepositorySharedTests.test.ts +++ b/test/repositories/ParkingRepositorySharedTests.test.ts @@ -1,7 +1,6 @@ import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; -import { InMemoryParkingRepository, PARKING_LOGGING_INTERVAL_MS, ParkingStructureID } from "../../src/repositories/InMemoryParkingRepository"; -import { IParkingStructure, IParkingStructureTimestampRecord } from "../../src/entities/ParkingRepositoryEntities"; -import { CircularQueue } from "../../src/types/CircularQueue"; +import { InMemoryParkingRepository, } from "../../src/repositories/InMemoryParkingRepository"; +import { IParkingStructure } from "../../src/entities/ParkingRepositoryEntities"; import { ParkingStructureCountOptions } from "../../src/repositories/ParkingGetterRepository"; import { ParkingGetterSetterRepository } from "../../src/repositories/ParkingGetterSetterRepository"; import { RedisParkingRepository } from "../../src/repositories/RedisParkingRepository"; @@ -38,7 +37,7 @@ class RedisParkingRepositoryHolder implements RepositoryHolder { } const repositoryImplementations = [ - // new InMemoryParkingRepositoryHolder(), + new InMemoryParkingRepositoryHolder(), new RedisParkingRepositoryHolder(), ]; @@ -165,38 +164,42 @@ describe.each(repositoryImplementations)('$name', (holder) => { expect(await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options)).toEqual([]); }); - // it("should calculate averages for intervals with manual historical data", async () => { - // await repository.addOrUpdateParkingStructure(testStructure); - // - // const now = Date.now(); - // jest.useFakeTimers().setSystemTime(now); - // - // const updates = [ - // { ...testStructure, spotsAvailable: 80, updatedTime: new Date(now + 1000) }, - // { ...testStructure, spotsAvailable: 70, updatedTime: new Date(now + 2000) }, - // ]; - // - // for (let i = 0; i < updates.length; i++) { - // jest.setSystemTime(now + (i + 1) * PARKING_LOGGING_INTERVAL_MS + 100); - // await repository.addOrUpdateParkingStructure(updates[i]); - // } - // - // const options: ParkingStructureCountOptions = { - // startUnixEpochMs: now, - // endUnixEpochMs: now + 4000000, // Large range to capture all data - // intervalMs: 1000000 - // }; - // - // const result = await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options); - // - // // Should have at least some historical data - // expect(result.length).toBeGreaterThan(0); - // if (result.length > 0) { - // expect(result[0]).toHaveProperty('fromUnixEpochMs'); - // expect(result[0]).toHaveProperty('toUnixEpochMs'); - // expect(result[0]).toHaveProperty('averageSpotsAvailable'); - // expect(typeof result[0].averageSpotsAvailable).toBe('number'); - // } - // }); + it("should calculate averages for intervals with manual historical data", async () => { + // Set logging interval to 0 so every update creates historical data + repository.setLoggingInterval(0); + + await repository.addOrUpdateParkingStructure(testStructure); + + const updates = [ + { ...testStructure, spotsAvailable: 80, updatedTime: new Date() }, + { ...testStructure, spotsAvailable: 70, updatedTime: new Date() }, + { ...testStructure, spotsAvailable: 60, updatedTime: new Date() }, + ]; + + // Add updates with small delays to ensure different timestamps + for (let i = 0; i < updates.length; i++) { + await repository.addOrUpdateParkingStructure(updates[i]); + await new Promise(resolve => setTimeout(resolve, 10)); // Small delay + } + + const now = Date.now(); + const options: ParkingStructureCountOptions = { + startUnixEpochMs: now - 10000, // Look back 10 seconds + endUnixEpochMs: now + 10000, // Look forward 10 seconds + intervalMs: 20000 // Single large interval + }; + + const result = await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options); + + // Should have at least some historical data + expect(result.length).toBeGreaterThan(0); + if (result.length > 0) { + expect(result[0]).toHaveProperty('fromUnixEpochMs'); + expect(result[0]).toHaveProperty('toUnixEpochMs'); + expect(result[0]).toHaveProperty('averageSpotsAvailable'); + expect(typeof result[0].averageSpotsAvailable).toBe('number'); + expect(result[0].averageSpotsAvailable).toBeGreaterThan(0); + } + }); }); });