Skip to content

Commit

Permalink
Configurable google rate limit / cooldown so we can test it
Browse files Browse the repository at this point in the history
  • Loading branch information
Lan2u committed Sep 20, 2024
1 parent 9fcf16c commit 638a203
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 33 deletions.
2 changes: 1 addition & 1 deletion src/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const Config = t.strict({
TURSO_TOKEN: t.union([t.undefined, t.string]),
TURSO_SYNC_URL: t.union([t.undefined, t.string]),
LOG_LEVEL: withDefaultIfEmpty(LogLevel, 'debug'),
QUIZ_RESULT_REFRESH_COOLDOWN_MS: withDefaultIfEmpty(
GOOGLE_RATELIMIT_MS: withDefaultIfEmpty(
tt.IntFromString,
(20 * 60 * 1000) as t.Int
),
Expand Down
3 changes: 2 additions & 1 deletion src/init-dependencies/init-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export const initDependencies = (
const sharedReadModel = initSharedReadModel(
dbClient,
logger,
pullGoogleSheetData(googleAuth)
pullGoogleSheetData(googleAuth),
conf.GOOGLE_RATELIMIT_MS
);

const deps: Dependencies = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {QzEvent} from '../../types/qz-event';
import {extractGoogleSheetData} from '../../training-sheets/google';
import {UUID} from 'io-ts-types';

const GOOGLE_UPDATE_INTERVAL_MS = 5 * 60 * 1000;

export type PullSheetData = (
logger: Logger,
trainingSheetId: string
Expand Down Expand Up @@ -63,7 +61,8 @@ export const asyncApplyExternalEventSources = (
logger: Logger,
currentState: BetterSQLite3Database,
pullGoogleSheetData: PullSheetData,
updateState: (event: DomainEvent) => void
updateState: (event: DomainEvent) => void,
googleRateLimitMs: number
) => {
return () => async () => {
logger.info('Applying external event sources...');
Expand All @@ -72,7 +71,7 @@ export const asyncApplyExternalEventSources = (
O.isNone(equipment.lastQuizSync) ||
(Date.now() as EpochTimestampMilliseconds) -
equipment.lastQuizSync.value >
GOOGLE_UPDATE_INTERVAL_MS
googleRateLimitMs
) {
logger.info(
'Triggering event update from google training sheets for %s...',
Expand Down
6 changes: 4 additions & 2 deletions src/read-models/shared-state/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export type SharedReadModel = {
export const initSharedReadModel = (
eventStoreClient: Client,
logger: Logger,
pullGoogleSheetData: PullSheetData
pullGoogleSheetData: PullSheetData,
googleRateLimitMs: number
): SharedReadModel => {
const readModelDb = drizzle(new Database());
createTables.forEach(statement => readModelDb.run(statement));
Expand All @@ -46,7 +47,8 @@ export const initSharedReadModel = (
logger,
readModelDb,
pullGoogleSheetData,
updateState_
updateState_,
googleRateLimitMs
),
members: {
get: getMember(readModelDb),
Expand Down
3 changes: 2 additions & 1 deletion tests/init-dependencies/happy-path-adapters.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export const happyPathAdapters: Dependencies = {
level: 'fatal',
timestamp: pino.stdTimeFunctions.isoTime,
}),
localPullGoogleSheetData
localPullGoogleSheetData,
120_000
),
logger: (() => undefined) as never as Logger,
rateLimitSendingOfEmails: TE.right,
Expand Down
3 changes: 2 additions & 1 deletion tests/read-models/test-framework.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export const initTestFramework = async (): Promise<TestFramework> => {
const sharedReadModel = initSharedReadModel(
dbClient,
logger,
localPullGoogleSheetData
localPullGoogleSheetData,
120_000
);
const frameworkCommitEvent = commitEvent(
dbClient,
Expand Down
124 changes: 101 additions & 23 deletions tests/training-sheets/process-events.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import {
EpochTimestampMilliseconds,
Equipment,
} from '../../src/read-models/shared-state/return-types';
import {DateTime} from 'luxon';
import {getSomeOrFail} from '../helpers';

const sortQuizResults = RA.sort({
Expand Down Expand Up @@ -69,28 +68,31 @@ const extractEvents = async (
return await pullNewEquipmentQuizResultsLocal(equipment);
};

type ApplyExternalEventsResults = {
startTime: EpochTimestampMilliseconds;
newEvents: DomainEvent[];
endTime: EpochTimestampMilliseconds;
equipmentAfter: Map<string, Equipment>;
};

const runAsyncApplyExternalEventSources = async (
logger: Logger,
framework: TestFramework
) => {
const startTime = DateTime.utc().toSeconds();
framework: TestFramework,
googleRateLimitMs: number
): Promise<ApplyExternalEventsResults> => {
const startTime = Date.now() as EpochTimestampMilliseconds;
const newEvents: DomainEvent[] = [];
await asyncApplyExternalEventSources(
logger,
framework.sharedReadModel.db,
localPullGoogleSheetData,
newEvents.push
newEvents.push,
googleRateLimitMs
)()();
const endTime = DateTime.utc().toSeconds();
const endTime = Date.now() as EpochTimestampMilliseconds;
const equipmentAfter = new Map(
framework.sharedReadModel.equipment.getAll().map(e => [e.id, e])
);
// Check that the last quiz sync property is updated to reflect
// that a quiz sync was preformed.
for (const equipment of equipmentAfter.values()) {
expect(equipment.lastQuizSync).toBeGreaterThan(startTime);
expect(equipment.lastQuizSync).toBeLessThan(endTime);
}
return {
startTime,
newEvents,
Expand All @@ -99,6 +101,15 @@ const runAsyncApplyExternalEventSources = async (
};
};

const checkLastQuizSync = (results: ApplyExternalEventsResults) => {
// Check that the last quiz sync property is updated to reflect
// that a quiz sync was preformed.
for (const equipment of results.equipmentAfter.values()) {
expect(equipment.lastQuizSync).toBeGreaterThan(results.startTime);
expect(equipment.lastQuizSync).toBeLessThan(results.endTime);
}
};

const checkLastQuizEventTimestamp = (
data: gsheetData.ManualParsed,
equipmentAfter: Equipment
Expand Down Expand Up @@ -258,18 +269,20 @@ describe('Training sheets worker', () => {
const addWithSheet = async (
name: string,
areaId: UUID,
trainingSheetId: string
trainingSheetId: O.Option<string>
) => {
const equipment = {
id: faker.string.uuid() as UUID,
name: name as NonEmptyString,
areaId,
};
await framework.commands.equipment.add(equipment);
await framework.commands.equipment.trainingSheet({
equipmentId: equipment.id,
trainingSheetId,
});
if (O.isSome(trainingSheetId)) {
await framework.commands.equipment.trainingSheet({
equipmentId: equipment.id,
trainingSheetId: trainingSheetId.value,
});
}
return {
...equipment,
trainingSheetId,
Expand All @@ -280,17 +293,19 @@ describe('Training sheets worker', () => {
const bambu = await addWithSheet(
'bambu',
createArea.id,
gsheetData.BAMBU.data.spreadsheetId!
O.some(gsheetData.BAMBU.data.spreadsheetId!)
);
const lathe = await addWithSheet(
'Metal Lathe',
createArea.id,
gsheetData.METAL_LATHE.data.spreadsheetId!
O.some(gsheetData.METAL_LATHE.data.spreadsheetId!)
);
const results = await runAsyncApplyExternalEventSources(
logger,
framework
framework,
10_000
);
checkLastQuizSync(results);
checkLastQuizEventTimestamp(
gsheetData.BAMBU,
results.equipmentAfter.get(bambu.id)!
Expand All @@ -299,19 +314,82 @@ describe('Training sheets worker', () => {
gsheetData.METAL_LATHE,
results.equipmentAfter.get(lathe.id)!
);
expect(results['newEvents']).toHaveLength(
expect(results.newEvents).toHaveLength(
gsheetData.BAMBU.entries.length +
gsheetData.METAL_LATHE.entries.length
);
});
it('Handle no equipment', async () => {
const results = await runAsyncApplyExternalEventSources(
logger,
framework
framework,
10_000
);
checkLastQuizSync(results);
expect(results.equipmentAfter).toHaveLength(0);
});
it('Rate limit equipment pull', () => {});
it('Handle equipment with no training sheet', async () => {
const bambu = await addWithSheet('bambu', createArea.id, O.none);
const results = await runAsyncApplyExternalEventSources(
logger,
framework,
10_000
);
checkLastQuizSync(results);
expect(
results.equipmentAfter.get(bambu.id)!.lastQuizResult
).toStrictEqual(O.none);
expect(results.newEvents).toHaveLength(0);
});
it('Rate limit equipment pull', async () => {
const bambu = await addWithSheet(
'bambu',
createArea.id,
O.some(gsheetData.BAMBU.data.spreadsheetId!)
);
const results1 = await runAsyncApplyExternalEventSources(
logger,
framework,
10_000
);
checkLastQuizSync(results1);
const results2 = await runAsyncApplyExternalEventSources(
logger,
framework,
10_000
);
expect(
results1.equipmentAfter.get(bambu.id)!.lastQuizSync
).toStrictEqual(results2.equipmentAfter.get(bambu.id)!.lastQuizSync);
expect(results1.newEvents.length).toBeGreaterThan(0);
expect(results2.newEvents).toHaveLength(0);
});
it('Repeat equipment pull no rate limit', async () => {
const bambu = await addWithSheet(
'bambu',
createArea.id,
O.some(gsheetData.BAMBU.data.spreadsheetId!)
);
const results1 = await runAsyncApplyExternalEventSources(
logger,
framework,
100
);
checkLastQuizSync(results1);

await new Promise(res => setTimeout(res, 1000));
const results2 = await runAsyncApplyExternalEventSources(
logger,
framework,
100
);
checkLastQuizSync(results2);
expect(results1.equipmentAfter.get(bambu.id)!.lastQuizSync).not.toEqual(
results2.equipmentAfter.get(bambu.id)!.lastQuizSync
);
expect(results1.newEvents.length).toBeGreaterThan(0);
expect(results2.newEvents).toHaveLength(0);
});
it('Handle equipment in different areas', () => {});
});
});
Expand Down

0 comments on commit 638a203

Please sign in to comment.