Study/benchmarks/remediationBenchmark.test.ts
Elijah e8dffc9692
Some checks failed
Verify and publish container / build-and-push (push) Failing after 33s
Remove obsolete project artifacts
2026-08-08 18:50:16 -07:00

145 lines
5.5 KiB
TypeScript

import { performance } from "node:perf_hooks";
import path from "node:path";
import Database from "better-sqlite3";
import { afterAll, describe, expect, it } from "vitest";
import { prisma } from "@/lib/db";
import { getActivitySummary } from "@/services/activityService";
import { listClasses } from "@/services/classService";
import { getStudyAvailabilityByClass } from "@/services/spacedRepetitionService";
const ACTIVITY_ROWS = 100_000;
const CARD_ROWS = 10_000;
const SETS = 5;
const REQUEST_BUDGET_MS = 250;
const NOW = new Date("2026-08-07T18:00:00.000Z");
afterAll(async () => {
await prisma.$disconnect();
});
function databasePath() {
const url = process.env.DATABASE_URL;
if (!url?.startsWith("file:./")) {
throw new Error("Benchmark requires the disposable Vitest database");
}
return path.resolve(process.cwd(), url.slice("file:".length));
}
function seedRealApplicationSchema() {
const database = new Database(databasePath());
try {
database.pragma("foreign_keys = ON");
const insertActivity = database.prepare(
'INSERT INTO "StudyActivity" ("id", "type", "occurredAt") VALUES (?, ?, ?)'
);
const insertDeck = database.prepare(
'INSERT INTO "Deck" ("id", "classId", "name", "sortOrder") VALUES (?, ?, ?, ?)'
);
const insertSet = database.prepare(
'INSERT INTO "SpacedRepetitionSet" ("id", "classId", "name", "newCardsPerDay", "sortOrder", "updatedAt") VALUES (?, ?, ?, ?, ?, ?)'
);
const insertMembership = database.prepare(
'INSERT INTO "SpacedRepetitionSetDeck" ("setId", "deckId", "sortOrder") VALUES (?, ?, ?)'
);
const insertCard = database.prepare(
'INSERT INTO "Flashcard" ("id", "deckId", "front", "back", "sortOrder") VALUES (?, ?, ?, ?, ?)'
);
const insertState = database.prepare(`
INSERT INTO "SpacedRepetitionCardState"
("id", "setId", "flashcardId", "due", "stability", "difficulty", "elapsedDays", "scheduledDays", "learningSteps", "reps", "lapses", "state", "firstReviewedAt", "lastReview", "createdAt", "updatedAt")
VALUES (?, ?, ?, ?, 1, 5, 1, ?, 0, 1, 0, ?, ?, ?, ?, ?)
`);
database.transaction(() => {
database.prepare(
'INSERT INTO "Class" ("id", "slug", "name", "sortOrder") VALUES (?, ?, ?, 0)'
).run("benchmark-class", "benchmark-class", "Benchmark Class");
for (let index = 0; index < ACTIVITY_ROWS; index += 1) {
const ageDays = index % 730;
const type = index % 2 === 0 ? "FLASHCARD" : "QUIZ_QUESTION";
insertActivity.run(
`activity-${index}`,
type,
NOW.getTime() - ageDays * 86_400_000
);
}
for (let setIndex = 0; setIndex < SETS; setIndex += 1) {
const deckId = `benchmark-deck-${setIndex}`;
const setId = `benchmark-set-${setIndex}`;
insertDeck.run(deckId, "benchmark-class", `Deck ${setIndex}`, setIndex);
insertSet.run(setId, "benchmark-class", `Set ${setIndex}`, 30, setIndex, NOW.getTime());
insertMembership.run(setId, deckId, 0);
for (let cardIndex = setIndex; cardIndex < CARD_ROWS; cardIndex += SETS) {
const cardId = `benchmark-card-${cardIndex}`;
const due = NOW.getTime() + ((cardIndex % 200) - 100) * 60_000;
const firstReviewed = NOW.getTime() - (cardIndex % 30) * 86_400_000;
const scheduledDays = cardIndex % 4 === 0 ? 1 : 0;
const state = cardIndex % 4;
insertCard.run(cardId, deckId, `Front ${cardIndex}`, `Back ${cardIndex}`, cardIndex);
insertState.run(
`benchmark-state-${cardIndex}`,
setId,
cardId,
due,
scheduledDays,
state,
firstReviewed,
firstReviewed,
firstReviewed,
NOW.getTime()
);
}
}
})();
} finally {
database.close();
}
}
async function measure<T>(label: string, operation: () => Promise<T>) {
const start = performance.now();
const value = await operation();
return {
label,
latencyMs: Number((performance.now() - start).toFixed(2)),
value,
};
}
describe("remediation performance condition", () => {
it("measures actual migrated services at the expected upper-use fixture", async () => {
seedRealApplicationSchema();
const activity = await measure("getActivitySummary", () => getActivitySummary(NOW));
const availability = await measure("getStudyAvailabilityByClass", () =>
getStudyAvailabilityByClass(NOW)
);
const classPolling = await measure("listClasses/navbar-poll", () => listClasses());
const measurements = [activity, availability, classPolling].map((result) => ({
label: result.label,
latencyMs: result.latencyMs,
}));
console.info(JSON.stringify({
fixture: { activityRows: ACTIVITY_ROWS, cards: CARD_ROWS, sets: SETS },
budgetMs: REQUEST_BUDGET_MS,
prismaServiceOperations: { activity: 1, availability: 1, classPolling: 2 },
measurements,
activity: {
displayedDays: activity.value.days.length,
currentStreak: activity.value.currentStreak,
},
readyCards: availability.value["benchmark-class"]?.readyCards,
}, null, 2));
expect(activity.value.days).toHaveLength(53 * 7);
expect(activity.value.currentStreak).toBeGreaterThan(53 * 7);
expect(availability.value["benchmark-class"]).toBeDefined();
expect(classPolling.value).toHaveLength(1);
for (const measurement of measurements) {
expect(measurement.latencyMs, measurement.label).toBeLessThanOrEqual(
REQUEST_BUDGET_MS
);
}
});
});