update timed loader to use class composition

This commit is contained in:
2025-04-11 16:52:27 -07:00
parent d1a47baea6
commit a0e0c19ca3
3 changed files with 35 additions and 38 deletions

View File

@@ -1,6 +1,5 @@
import { ShuttleRepositoryLoader } from "../loaders/ShuttleRepositoryLoader";
import { ETANotificationScheduler } from "../notifications/schedulers/ETANotificationScheduler"; import { ETANotificationScheduler } from "../notifications/schedulers/ETANotificationScheduler";
import { TimedApiBasedShuttleRepositoryLoader } from "../loaders/TimedApiBasedShuttleRepositoryLoader"; import { TimedApiBasedRepositoryLoader } from "../loaders/TimedApiBasedRepositoryLoader";
import { UnoptimizedInMemoryShuttleRepository } from "../repositories/UnoptimizedInMemoryShuttleRepository"; import { UnoptimizedInMemoryShuttleRepository } from "../repositories/UnoptimizedInMemoryShuttleRepository";
import { RedisNotificationRepository } from "../repositories/RedisNotificationRepository"; import { RedisNotificationRepository } from "../repositories/RedisNotificationRepository";
import { NotificationRepository } from "../repositories/NotificationRepository"; import { NotificationRepository } from "../repositories/NotificationRepository";
@@ -27,7 +26,7 @@ export class InterchangeSystem {
constructor( constructor(
public name: string, public name: string,
public id: string, public id: string,
public shuttleDataLoader: ShuttleRepositoryLoader, public shuttleTimedDataLoader: TimedApiBasedRepositoryLoader,
public shuttleRepository: ShuttleGetterSetterRepository, public shuttleRepository: ShuttleGetterSetterRepository,
public notificationScheduler: ETANotificationScheduler, public notificationScheduler: ETANotificationScheduler,
public notificationRepository: NotificationRepository, public notificationRepository: NotificationRepository,
@@ -43,12 +42,15 @@ export class InterchangeSystem {
args: InterchangeSystemBuilderArguments, args: InterchangeSystemBuilderArguments,
) { ) {
const shuttleRepository = new UnoptimizedInMemoryShuttleRepository(); const shuttleRepository = new UnoptimizedInMemoryShuttleRepository();
const shuttleDataLoader = new TimedApiBasedShuttleRepositoryLoader( const shuttleDataLoader = new ApiBasedShuttleRepositoryLoader(
args.passioSystemId, args.passioSystemId,
args.id, args.id,
shuttleRepository shuttleRepository
); );
await shuttleDataLoader.start(); const timedShuttleDataLoader = new TimedApiBasedRepositoryLoader(
shuttleDataLoader,
);
await timedShuttleDataLoader.start();
const notificationRepository = new RedisNotificationRepository(); const notificationRepository = new RedisNotificationRepository();
await notificationRepository.connect(); await notificationRepository.connect();
@@ -62,7 +64,7 @@ export class InterchangeSystem {
return new InterchangeSystem( return new InterchangeSystem(
args.name, args.name,
args.id, args.id,
shuttleDataLoader, timedShuttleDataLoader,
shuttleRepository, shuttleRepository,
notificationScheduler, notificationScheduler,
notificationRepository, notificationRepository,
@@ -84,6 +86,11 @@ export class InterchangeSystem {
args.id, args.id,
shuttleRepository shuttleRepository
); );
// Note that this loader should not be started,
// so the test data doesn't get overwritten
const timedShuttleLoader = new TimedApiBasedRepositoryLoader(
shuttleDataLoader,
);
const notificationRepository = new InMemoryNotificationRepository(); const notificationRepository = new InMemoryNotificationRepository();
const notificationScheduler = new ETANotificationScheduler( const notificationScheduler = new ETANotificationScheduler(
@@ -96,7 +103,7 @@ export class InterchangeSystem {
return new InterchangeSystem( return new InterchangeSystem(
args.name, args.name,
args.id, args.id,
shuttleDataLoader, timedShuttleLoader,
shuttleRepository, shuttleRepository,
notificationScheduler, notificationScheduler,
notificationRepository, notificationRepository,

View File

@@ -1,5 +1,4 @@
import { ShuttleGetterSetterRepository } from "../repositories/ShuttleGetterSetterRepository"; import { RepositoryLoader } from "./RepositoryLoader";
import { ApiBasedShuttleRepositoryLoader } from "./ApiBasedShuttleRepositoryLoader";
// Ideas to break this into smaller pieces in the future: // Ideas to break this into smaller pieces in the future:
// Have one repository data loader running for each supported system // Have one repository data loader running for each supported system
@@ -15,18 +14,15 @@ import { ApiBasedShuttleRepositoryLoader } from "./ApiBasedShuttleRepositoryLoad
// - OrderedStops: reload every few minutes // - OrderedStops: reload every few minutes
// - Systems: reload once a day // - Systems: reload once a day
export class TimedApiBasedShuttleRepositoryLoader extends ApiBasedShuttleRepositoryLoader { export class TimedApiBasedRepositoryLoader {
private shouldBeRunning: boolean = false; private shouldBeRunning: boolean = false;
private timer: any; private timer: any;
readonly timeout = 10000; readonly timeout = 10000;
constructor( constructor(
public passioSystemId: string, public loader: RepositoryLoader,
public systemIdForConstructedData: string,
repository: ShuttleGetterSetterRepository,
) { ) {
super(passioSystemId, systemIdForConstructedData, repository);
this.startFetchDataAndUpdate = this.startFetchDataAndUpdate.bind(this); this.startFetchDataAndUpdate = this.startFetchDataAndUpdate.bind(this);
} }
@@ -48,14 +44,7 @@ export class TimedApiBasedShuttleRepositoryLoader extends ApiBasedShuttleReposit
if (!this.shouldBeRunning) return; if (!this.shouldBeRunning) return;
try { try {
await this.fetchAndUpdateRouteDataForSystem(); await this.loader.fetchAndUpdateAll();
await this.fetchAndUpdateStopAndPolylineDataForRoutesInSystem();
await this.fetchAndUpdateShuttleDataForSystem();
// Because ETA method doesn't support pruning yet,
// add a call to the clear method here
await this.repository.clearEtaData();
await this.fetchAndUpdateEtaDataForExistingStopsForSystem();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }

View File

@@ -1,10 +1,11 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals"; import { afterEach, beforeAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
import { TimedApiBasedShuttleRepositoryLoader } from "../../src/loaders/TimedApiBasedShuttleRepositoryLoader"; import { TimedApiBasedRepositoryLoader } from "../../src/loaders/TimedApiBasedRepositoryLoader";
import { resetGlobalFetchMockJson } from "../testHelpers/fetchMockHelpers"; import { resetGlobalFetchMockJson } from "../testHelpers/fetchMockHelpers";
import { UnoptimizedInMemoryShuttleRepository } from "../../src/repositories/UnoptimizedInMemoryShuttleRepository"; import { UnoptimizedInMemoryShuttleRepository } from "../../src/repositories/UnoptimizedInMemoryShuttleRepository";
import { ApiBasedShuttleRepositoryLoader } from "../../src/loaders/ApiBasedShuttleRepositoryLoader";
describe("TimedApiBasedRepositoryLoader", () => { describe("TimedApiBasedRepositoryLoader", () => {
let loader: TimedApiBasedShuttleRepositoryLoader; let timedLoader: TimedApiBasedRepositoryLoader;
let spies: any; let spies: any;
beforeAll(() => { beforeAll(() => {
@@ -15,17 +16,17 @@ describe("TimedApiBasedRepositoryLoader", () => {
beforeEach(() => { beforeEach(() => {
resetGlobalFetchMockJson(); resetGlobalFetchMockJson();
loader = new TimedApiBasedShuttleRepositoryLoader( const mockLoader = new ApiBasedShuttleRepositoryLoader(
"1", "1",
"1", "1",
new UnoptimizedInMemoryShuttleRepository() new UnoptimizedInMemoryShuttleRepository(),
);
timedLoader = new TimedApiBasedRepositoryLoader(
mockLoader,
); );
spies = { spies = {
fetchAndUpdateRouteDataForSystem: jest.spyOn(loader, 'fetchAndUpdateRouteDataForSystem'), fetchAndUpdateAll: jest.spyOn(mockLoader, 'fetchAndUpdateAll'),
fetchAndUpdateStopAndPolylineDataForRoutesInSystem: jest.spyOn(loader, 'fetchAndUpdateStopAndPolylineDataForRoutesInSystem'),
fetchAndUpdateShuttleDataForSystem: jest.spyOn(loader, 'fetchAndUpdateShuttleDataForSystem'),
fetchAndUpdateEtaDataForExistingStopsForSystem: jest.spyOn(loader, 'fetchAndUpdateEtaDataForExistingStopsForSystem')
}; };
Object.values(spies).forEach((spy: any) => { Object.values(spies).forEach((spy: any) => {
@@ -40,20 +41,20 @@ describe("TimedApiBasedRepositoryLoader", () => {
describe("start", () => { describe("start", () => {
it("should update internal state, call data fetching methods, and start a timer", async () => { it("should update internal state, call data fetching methods, and start a timer", async () => {
await loader.start(); await timedLoader.start();
expect(loader["shouldBeRunning"]).toBe(true); expect(timedLoader["shouldBeRunning"]).toBe(true);
Object.values(spies).forEach((spy: any) => { Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalled(); expect(spy).toHaveBeenCalled();
}); });
expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), loader.timeout); expect(setTimeout).toHaveBeenCalledWith(expect.any(Function), timedLoader.timeout);
expect(loader.timeout).not.toBeUndefined(); expect(timedLoader.timeout).not.toBeUndefined();
}); });
it("does nothing if timer is already running", async () => { it("does nothing if timer is already running", async () => {
await loader.start(); await timedLoader.start();
await loader.start(); await timedLoader.start();
Object.values(spies).forEach((spy: any) => { Object.values(spies).forEach((spy: any) => {
expect(spy).toHaveBeenCalledTimes(1); expect(spy).toHaveBeenCalledTimes(1);
@@ -63,8 +64,8 @@ describe("TimedApiBasedRepositoryLoader", () => {
describe("stop", () => { describe("stop", () => {
it("should update internal state", async () => { it("should update internal state", async () => {
loader.stop(); timedLoader.stop();
expect(loader['shouldBeRunning']).toBe(false); expect(timedLoader['shouldBeRunning']).toBe(false);
}); });
}); });
}); });