Add setLoggingInterval method and implementation for tests and other purposes.

Let users control the logging interval of both parking repositories.
This commit is contained in:
2025-07-02 19:47:44 -04:00
parent 868a9f3b1d
commit 19336ce6ec
4 changed files with 57 additions and 42 deletions

View File

@@ -17,6 +17,7 @@ export const MAX_NUM_ENTRIES = 2016;
export class InMemoryParkingRepository implements ParkingGetterSetterRepository { export class InMemoryParkingRepository implements ParkingGetterSetterRepository {
private dataLastAdded: Map<ParkingStructureID, Date> = new Map(); private dataLastAdded: Map<ParkingStructureID, Date> = new Map();
private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS;
constructor( constructor(
private structures: Map<ParkingStructureID, IParkingStructure> = new Map(), private structures: Map<ParkingStructureID, IParkingStructure> = new Map(),
@@ -33,9 +34,9 @@ export class InMemoryParkingRepository implements ParkingGetterSetterRepository
const now = Date.now(); const now = Date.now();
const lastAdded = this.dataLastAdded.get(structure.id); const lastAdded = this.dataLastAdded.get(structure.id);
function parkingLoggingIntervalExceeded() { const parkingLoggingIntervalExceeded = () => {
return !lastAdded || (now - lastAdded.getTime()) >= PARKING_LOGGING_INTERVAL_MS; return !lastAdded || (now - lastAdded.getTime()) >= this.loggingIntervalMs;
} };
if (parkingLoggingIntervalExceeded()) { if (parkingLoggingIntervalExceeded()) {
const timestampRecord: IParkingStructureTimestampRecord = { const timestampRecord: IParkingStructureTimestampRecord = {
@@ -131,4 +132,8 @@ export class InMemoryParkingRepository implements ParkingGetterSetterRepository
averageSpotsAvailable averageSpotsAvailable
}; };
}; };
setLoggingInterval = (intervalMs: number): void => {
this.loggingIntervalMs = intervalMs;
};
} }

View File

@@ -7,4 +7,6 @@ export interface ParkingGetterSetterRepository extends ParkingGetterRepository {
removeParkingStructureIfExists(id: string): Promise<IParkingStructure | null>; removeParkingStructureIfExists(id: string): Promise<IParkingStructure | null>;
clearParkingStructureData(): Promise<void>; clearParkingStructureData(): Promise<void>;
setLoggingInterval(intervalMs: number): void;
} }

View File

@@ -1,4 +1,4 @@
import { createClient, RedisClientType } from 'redis'; import { createClient } from 'redis';
import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository"; import { ParkingGetterSetterRepository } from "./ParkingGetterSetterRepository";
import { IParkingStructure, IParkingStructureTimestampRecord } from "../entities/ParkingRepositoryEntities"; import { IParkingStructure, IParkingStructureTimestampRecord } from "../entities/ParkingRepositoryEntities";
import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository"; import { HistoricalParkingAverageQueryResult, ParkingStructureCountOptions } from "./ParkingGetterRepository";
@@ -10,6 +10,7 @@ export const PARKING_LOGGING_INTERVAL_MS = 600000;
export class RedisParkingRepository implements ParkingGetterSetterRepository { export class RedisParkingRepository implements ParkingGetterSetterRepository {
private dataLastAdded: Map<ParkingStructureID, Date> = new Map(); private dataLastAdded: Map<ParkingStructureID, Date> = new Map();
private loggingIntervalMs = PARKING_LOGGING_INTERVAL_MS;
constructor( constructor(
private redisClient = createClient({ private redisClient = createClient({
@@ -61,7 +62,7 @@ export class RedisParkingRepository implements ParkingGetterSetterRepository {
const lastAdded = this.dataLastAdded.get(structure.id); const lastAdded = this.dataLastAdded.get(structure.id);
const parkingLoggingIntervalExceeded = () => { const parkingLoggingIntervalExceeded = () => {
return !lastAdded || (now - lastAdded.getTime()) >= PARKING_LOGGING_INTERVAL_MS; return !lastAdded || (now - lastAdded.getTime()) >= this.loggingIntervalMs;
}; };
if (parkingLoggingIntervalExceeded()) { if (parkingLoggingIntervalExceeded()) {
@@ -262,4 +263,8 @@ export class RedisParkingRepository implements ParkingGetterSetterRepository {
averageSpotsAvailable averageSpotsAvailable
}; };
}; };
setLoggingInterval = (intervalMs: number): void => {
this.loggingIntervalMs = intervalMs;
};
} }

View File

@@ -1,7 +1,6 @@
import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { InMemoryParkingRepository, PARKING_LOGGING_INTERVAL_MS, ParkingStructureID } from "../../src/repositories/InMemoryParkingRepository"; import { InMemoryParkingRepository, } from "../../src/repositories/InMemoryParkingRepository";
import { IParkingStructure, IParkingStructureTimestampRecord } from "../../src/entities/ParkingRepositoryEntities"; import { IParkingStructure } from "../../src/entities/ParkingRepositoryEntities";
import { CircularQueue } from "../../src/types/CircularQueue";
import { ParkingStructureCountOptions } from "../../src/repositories/ParkingGetterRepository"; import { ParkingStructureCountOptions } from "../../src/repositories/ParkingGetterRepository";
import { ParkingGetterSetterRepository } from "../../src/repositories/ParkingGetterSetterRepository"; import { ParkingGetterSetterRepository } from "../../src/repositories/ParkingGetterSetterRepository";
import { RedisParkingRepository } from "../../src/repositories/RedisParkingRepository"; import { RedisParkingRepository } from "../../src/repositories/RedisParkingRepository";
@@ -38,7 +37,7 @@ class RedisParkingRepositoryHolder implements RepositoryHolder {
} }
const repositoryImplementations = [ const repositoryImplementations = [
// new InMemoryParkingRepositoryHolder(), new InMemoryParkingRepositoryHolder(),
new RedisParkingRepositoryHolder(), new RedisParkingRepositoryHolder(),
]; ];
@@ -165,38 +164,42 @@ describe.each(repositoryImplementations)('$name', (holder) => {
expect(await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options)).toEqual([]); expect(await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options)).toEqual([]);
}); });
// it("should calculate averages for intervals with manual historical data", async () => { it("should calculate averages for intervals with manual historical data", async () => {
// await repository.addOrUpdateParkingStructure(testStructure); // Set logging interval to 0 so every update creates historical data
// repository.setLoggingInterval(0);
// const now = Date.now();
// jest.useFakeTimers().setSystemTime(now); await repository.addOrUpdateParkingStructure(testStructure);
//
// const updates = [ const updates = [
// { ...testStructure, spotsAvailable: 80, updatedTime: new Date(now + 1000) }, { ...testStructure, spotsAvailable: 80, updatedTime: new Date() },
// { ...testStructure, spotsAvailable: 70, updatedTime: new Date(now + 2000) }, { ...testStructure, spotsAvailable: 70, updatedTime: new Date() },
// ]; { ...testStructure, spotsAvailable: 60, updatedTime: new Date() },
// ];
// for (let i = 0; i < updates.length; i++) {
// jest.setSystemTime(now + (i + 1) * PARKING_LOGGING_INTERVAL_MS + 100); // Add updates with small delays to ensure different timestamps
// await repository.addOrUpdateParkingStructure(updates[i]); for (let i = 0; i < updates.length; i++) {
// } await repository.addOrUpdateParkingStructure(updates[i]);
// await new Promise(resolve => setTimeout(resolve, 10)); // Small delay
// const options: ParkingStructureCountOptions = { }
// startUnixEpochMs: now,
// endUnixEpochMs: now + 4000000, // Large range to capture all data const now = Date.now();
// intervalMs: 1000000 const options: ParkingStructureCountOptions = {
// }; startUnixEpochMs: now - 10000, // Look back 10 seconds
// endUnixEpochMs: now + 10000, // Look forward 10 seconds
// const result = await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options); intervalMs: 20000 // Single large interval
// };
// // Should have at least some historical data
// expect(result.length).toBeGreaterThan(0); const result = await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options);
// if (result.length > 0) {
// expect(result[0]).toHaveProperty('fromUnixEpochMs'); // Should have at least some historical data
// expect(result[0]).toHaveProperty('toUnixEpochMs'); expect(result.length).toBeGreaterThan(0);
// expect(result[0]).toHaveProperty('averageSpotsAvailable'); if (result.length > 0) {
// expect(typeof result[0].averageSpotsAvailable).toBe('number'); 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);
}
});
}); });
}); });