mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
test(measurements): mock the status re-warm where the mutation tail imports it
The bulk-delete, restore and by-external-ids suites mocked `invalidateStatusInsightsForTypes` on the comprehensive-generate barrel, while the shared post-mutation tail imports it from status-invalidation. The mock was never called, the real re-warm ran the capability loader against a stub database, and the "no invalidate" assertion could not fail. The mock now sits on the imported module; the positive cases assert the re-warm with the distinct types, the zero-row cases assert its absence.
This commit is contained in:
1 parent
863d32af5a
commit
00f6eb6dcb
3 files changed
+35
-6
No files matched your search
@@ -37,7 +37,10 @@ vi.mock("@/lib/idempotency", () => ({
|
||||
vi.mock("@/lib/cache/invalidate", () => ({
|
||||
invalidateUserMeasurements: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/insights/comprehensive-generate", () => ({
|
||||
// The status re-warm the shared post-mutation tail fires. Mocked at the
|
||||
// module the tail imports it from; a mock anywhere else is never called, and
|
||||
// the real one runs the AI capability loader against this file's stub db.
|
||||
vi.mock("@/lib/insights/status-invalidation", () => ({
|
||||
invalidateStatusInsightsForTypes: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
vi.mock("@/lib/rollups/measurement-rollups", async () => {
|
||||
@@ -68,7 +71,7 @@ import { prisma } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/comprehensive-generate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/status-invalidation";
|
||||
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
|
||||
|
||||
const SESSION_OK = {
|
||||
@@ -149,6 +152,12 @@ describe("POST /api/measurements/bulk-delete", () => {
|
||||
|
||||
// 4 rows in, 3 distinct (type, day) recomputes out.
|
||||
expect(recomputeBucketsForMeasurement).toHaveBeenCalledTimes(3);
|
||||
// And one status re-warm over the distinct types.
|
||||
expect(invalidateStatusInsightsForTypes).toHaveBeenCalledTimes(1);
|
||||
expect(invalidateStatusInsightsForTypes).toHaveBeenCalledWith("user-1", [
|
||||
"WEIGHT",
|
||||
"PULSE",
|
||||
]);
|
||||
});
|
||||
|
||||
it("is a no-op (deleted: 0) when no id is owned — no rollup, no invalidate", async () => {
|
||||
@@ -163,6 +172,7 @@ describe("POST /api/measurements/bulk-delete", () => {
|
||||
expect(body.data.deleted).toBe(0);
|
||||
expect(invalidateUserMeasurements).not.toHaveBeenCalled();
|
||||
expect(recomputeBucketsForMeasurement).not.toHaveBeenCalled();
|
||||
expect(invalidateStatusInsightsForTypes).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a batch over the 200-id cap with 422", async () => {
|
||||
|
||||
@@ -28,7 +28,10 @@ vi.mock("@/lib/auth/audit", () => ({
|
||||
vi.mock("@/lib/cache/invalidate", () => ({
|
||||
invalidateUserMeasurements: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/insights/comprehensive-generate", () => ({
|
||||
// The status re-warm the shared post-mutation tail fires. Mocked at the
|
||||
// module the tail imports it from; a mock anywhere else is never called, and
|
||||
// the real one runs the AI capability loader against this file's stub db.
|
||||
vi.mock("@/lib/insights/status-invalidation", () => ({
|
||||
invalidateStatusInsightsForTypes: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
vi.mock("@/lib/rollups/measurement-rollups", async () => {
|
||||
@@ -57,7 +60,7 @@ import { DELETE } from "../route";
|
||||
import { prisma } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/comprehensive-generate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/status-invalidation";
|
||||
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
|
||||
|
||||
const SESSION_OK = {
|
||||
@@ -112,6 +115,12 @@ describe("DELETE /api/measurements/by-external-ids", () => {
|
||||
externalId: { in: ["uuid-aaa"] },
|
||||
deletedAt: null,
|
||||
});
|
||||
|
||||
// The deleted row's type is re-warmed; the zero-row case below is the
|
||||
// twin that must not reach it.
|
||||
expect(invalidateStatusInsightsForTypes).toHaveBeenCalledWith("user-1", [
|
||||
"WEIGHT",
|
||||
]);
|
||||
});
|
||||
|
||||
it("is a no-op for a foreign-origin externalId that collides on a non-app-minted row", async () => {
|
||||
|
||||
@@ -37,7 +37,10 @@ vi.mock("@/lib/idempotency", () => ({
|
||||
vi.mock("@/lib/cache/invalidate", () => ({
|
||||
invalidateUserMeasurements: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/insights/comprehensive-generate", () => ({
|
||||
// The status re-warm the shared post-mutation tail fires. Mocked at the
|
||||
// module the tail imports it from; a mock anywhere else is never called, and
|
||||
// the real one runs the AI capability loader against this file's stub db.
|
||||
vi.mock("@/lib/insights/status-invalidation", () => ({
|
||||
invalidateStatusInsightsForTypes: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
vi.mock("@/lib/rollups/measurement-rollups", async () => {
|
||||
@@ -68,7 +71,7 @@ import { prisma } from "@/lib/db";
|
||||
import { getSession } from "@/lib/auth/session";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { invalidateUserMeasurements } from "@/lib/cache/invalidate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/comprehensive-generate";
|
||||
import { invalidateStatusInsightsForTypes } from "@/lib/insights/status-invalidation";
|
||||
import { recomputeBucketsForMeasurement } from "@/lib/rollups/measurement-rollups";
|
||||
|
||||
const SESSION_OK = {
|
||||
@@ -144,6 +147,12 @@ describe("POST /api/measurements/restore", () => {
|
||||
|
||||
// 4 rows in, 3 distinct (type, day) recomputes out.
|
||||
expect(recomputeBucketsForMeasurement).toHaveBeenCalledTimes(3);
|
||||
// And one status re-warm over the distinct types.
|
||||
expect(invalidateStatusInsightsForTypes).toHaveBeenCalledTimes(1);
|
||||
expect(invalidateStatusInsightsForTypes).toHaveBeenCalledWith("user-1", [
|
||||
"WEIGHT",
|
||||
"PULSE",
|
||||
]);
|
||||
});
|
||||
|
||||
it("is a no-op (restored: 0) when no id is owned + tombstoned — no rollup, no invalidate", async () => {
|
||||
@@ -158,6 +167,7 @@ describe("POST /api/measurements/restore", () => {
|
||||
expect(body.data.restored).toBe(0);
|
||||
expect(invalidateUserMeasurements).not.toHaveBeenCalled();
|
||||
expect(recomputeBucketsForMeasurement).not.toHaveBeenCalled();
|
||||
expect(invalidateStatusInsightsForTypes).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("rejects a batch over the 200-id cap with 422", async () => {
|
||||
|
||||
Reference in new issue
Block a user