import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { InMemoryParkingRepository, } from "../../src/repositories/parking/InMemoryParkingRepository"; import { IParkingStructure } from "../../src/entities/ParkingRepositoryEntities"; import { ParkingStructureCountOptions } from "../../src/repositories/parking/ParkingGetterRepository"; import { ParkingGetterSetterRepository } from "../../src/repositories/parking/ParkingGetterSetterRepository"; import { RedisParkingRepository } from "../../src/repositories/parking/RedisParkingRepository"; interface RepositoryHolder { name: string; factory(): Promise; teardown(): Promise; } class InMemoryParkingRepositoryHolder implements RepositoryHolder { name = 'InMemoryParkingRepository'; factory = async () => { return new InMemoryParkingRepository(); }; teardown = async () => {}; } class RedisParkingRepositoryHolder implements RepositoryHolder { repo: RedisParkingRepository | undefined; name = 'RedisParkingRepository'; factory = async () => { this.repo = new RedisParkingRepository(); await this.repo.connect(); return this.repo; }; teardown = async () => { if (this.repo) { await this.repo.clearAllData(); await this.repo.disconnect(); } }; } const repositoryImplementations = [ new InMemoryParkingRepositoryHolder(), new RedisParkingRepositoryHolder(), ]; describe.each(repositoryImplementations)('$name', (holder) => { let repository: ParkingGetterSetterRepository; const testStructure: IParkingStructure = { coordinates: { latitude: 33.794795, longitude: -117.850807, }, spotsAvailable: 0, id: "1", name: "Anderson Parking Structure", capacity: 100, address: "300 E Walnut Ave, Orange, CA 92867", updatedTime: new Date(), }; beforeEach(async () => { repository = await holder.factory(); jest.useRealTimers(); }); afterEach(async () => { await holder.teardown(); jest.useRealTimers(); }); describe("addOrUpdateParkingStructure", () => { it("should add a new parking structure", async () => { await repository.addOrUpdateParkingStructure(testStructure); const result = await repository.getParkingStructureById(testStructure.id); expect(result).toEqual(testStructure); }); it("should update existing parking structure", async () => { await repository.addOrUpdateParkingStructure(testStructure); const updatedStructure = { ...testStructure, name: "Updated Garage" }; await repository.addOrUpdateParkingStructure(updatedStructure); const result = await repository.getParkingStructureById(testStructure.id); expect(result).toEqual(updatedStructure); }); }); describe("removeParkingStructureIfExists", () => { it("should remove existing parking structure and return it", async () => { await repository.addOrUpdateParkingStructure(testStructure); const removed = await repository.removeParkingStructureIfExists(testStructure.id); expect(removed).toEqual(testStructure); const result = await repository.getParkingStructureById(testStructure.id); expect(result).toBeNull(); }); it("should return null when removing non-existent structure", async () => { const result = await repository.removeParkingStructureIfExists("non-existent"); expect(result).toBeNull(); }); }); describe("clearParkingStructureData", () => { it("should remove all parking structures", async () => { const structures = [ testStructure, { ...testStructure, id: "test-id-2", name: "Second Garage" } ]; for (const structure of structures) { await repository.addOrUpdateParkingStructure(structure); } await repository.clearParkingStructureData(); const result = await repository.getParkingStructures(); expect(result).toHaveLength(0); }); }); describe("getParkingStructures", () => { it("should return empty array when no structures exist", async () => { const result = await repository.getParkingStructures(); expect(result).toEqual([]); }); it("should return all added structures", async () => { const structures = [ testStructure, { ...testStructure, id: "test-id-2", name: "Second Garage" } ]; for (const structure of structures) { await repository.addOrUpdateParkingStructure(structure); } const result = await repository.getParkingStructures(); expect(result).toHaveLength(2); expect(result).toEqual(expect.arrayContaining(structures)); }); }); describe("getParkingStructureById", () => { it("should return null for non-existent structure", async () => { const result = await repository.getParkingStructureById("non-existent"); expect(result).toBeNull(); }); it("should return structure by id", async () => { await repository.addOrUpdateParkingStructure(testStructure); const result = await repository.getParkingStructureById(testStructure.id); expect(result).toEqual(testStructure); }); }); describe("getHistoricalAveragesOfParkingStructureCounts", () => { it("should return empty array for non-existent structure or no data", async () => { const options: ParkingStructureCountOptions = { from: new Date(1000), to: new Date(2000), intervalMs: 500 }; expect(await repository.getHistoricalAveragesOfParkingStructureCounts("non-existent", options)).toEqual([]); await repository.addOrUpdateParkingStructure(testStructure); expect(await repository.getHistoricalAveragesOfParkingStructureCounts(testStructure.id, options)).toEqual([]); }); 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() }, ]; for (let i = 0; i < updates.length; i++) { // Ensure that different timestamps are created, even after adding the first test structure await new Promise((resolve) => setTimeout(resolve, 200)); await repository.addOrUpdateParkingStructure(updates[i]); } const now = Date.now(); const options: ParkingStructureCountOptions = { from: new Date(now - 10000), // Look back 10 seconds to: new Date(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).toEqual(1); if (result.length > 0) { expect(result[0]).toHaveProperty('from'); expect(result[0]).toHaveProperty('to'); expect(result[0].from).toBeInstanceOf(Date); expect(result[0].to).toBeInstanceOf(Date); expect(result[0]).toHaveProperty('averageSpotsAvailable'); expect(result[0].averageSpotsAvailable).toBeCloseTo(52.5); } }); }); });