mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-07 04:12:04 +00:00
fix(sources): stop listing a source whose readings were all deleted
The in-use probe matched tombstoned readings, so a source stayed marked on the priority ladder after every reading from it was deleted. Both the read and the save response of the source-priority route go through this one probe.
This commit is contained in:
1 parent
27dfc6a617
commit
dcbd564962
3 files changed
+84
-2
No files matched your search
@@ -38,6 +38,21 @@ describe("getSourcesInUse", () => {
|
||||
expect(await getSourcesInUse("u1")).toEqual(["MANUAL"]);
|
||||
});
|
||||
|
||||
it("does not count a source whose readings were all deleted", async () => {
|
||||
// The probe answers like the database: a MANUAL row exists, but it is a
|
||||
// tombstone, so only a probe that admits deleted rows finds it.
|
||||
vi.mocked(prisma.measurement.findFirst).mockImplementation(((args: {
|
||||
where: { source: string; deletedAt?: null };
|
||||
}) =>
|
||||
Promise.resolve(
|
||||
args.where.source === "MANUAL" && !("deletedAt" in args.where)
|
||||
? { id: "m-deleted" }
|
||||
: null,
|
||||
)) as never);
|
||||
|
||||
expect(await getSourcesInUse("u1")).toEqual([]);
|
||||
});
|
||||
|
||||
it("lists a connected integration that has not delivered yet", async () => {
|
||||
vi.mocked(prisma.measurement.findFirst).mockResolvedValue(null);
|
||||
vi.mocked(prisma.withingsConnection.findUnique).mockResolvedValue({
|
||||
|
||||
@@ -17,7 +17,8 @@ export const RANKABLE_SOURCES: readonly MeasurementSource[] = Array.from(
|
||||
* one measurement from, or an integration it has connected that has not
|
||||
* delivered yet.
|
||||
*
|
||||
* The Settings ladder lists only these. Ranking a source the person has never
|
||||
* Deleted (tombstoned) readings do not count. The Settings ladder lists only
|
||||
* these. Ranking a source the person has never
|
||||
* used decides nothing, and ten of them per metric buried the two that do.
|
||||
* The ladders themselves stay whole: hidden sources keep their slot, so a
|
||||
* source that starts delivering later lands where the ladder already put it.
|
||||
@@ -33,7 +34,12 @@ export async function getSourcesInUse(
|
||||
Promise.all(
|
||||
RANKABLE_SOURCES.map((source) =>
|
||||
prisma.measurement
|
||||
.findFirst({ where: { userId, source }, select: { id: true } })
|
||||
.findFirst({
|
||||
// A deleted reading is not data: a source whose readings were
|
||||
// all deleted is no longer in use.
|
||||
where: { userId, source, deletedAt: null },
|
||||
select: { id: true },
|
||||
})
|
||||
.then((row) => (row ? source : null)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* The sources the Settings ladder lists, against real Postgres.
|
||||
*
|
||||
* A source counts while the account holds a live reading from it. Deleting
|
||||
* readings tombstones them (`deletedAt`), so the probe has to skip the
|
||||
* tombstones or a source whose readings were all deleted stays marked as in
|
||||
* use for good.
|
||||
*/
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
import { getPrismaClient, truncateAllTables } from "./setup";
|
||||
|
||||
beforeEach(async () => {
|
||||
await truncateAllTables(getPrismaClient());
|
||||
});
|
||||
|
||||
async function seed() {
|
||||
const prisma = getPrismaClient();
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
username: "sources-in-use",
|
||||
email: "sources-in-use@example.test",
|
||||
role: "USER",
|
||||
},
|
||||
});
|
||||
const reading = (source: "MANUAL" | "APPLE_HEALTH", minutes: number) =>
|
||||
prisma.measurement.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
type: "WEIGHT",
|
||||
value: 80,
|
||||
unit: "kg",
|
||||
source,
|
||||
measuredAt: new Date(Date.UTC(2026, 4, 12, 7, minutes)),
|
||||
},
|
||||
});
|
||||
return { prisma, user, reading };
|
||||
}
|
||||
|
||||
describe("getSourcesInUse", () => {
|
||||
it("lists a source with a live reading", async () => {
|
||||
const { user, reading } = await seed();
|
||||
await reading("MANUAL", 0);
|
||||
const { getSourcesInUse } =
|
||||
await import("@/lib/integrations/sources-in-use");
|
||||
expect(await getSourcesInUse(user.id)).toEqual(["MANUAL"]);
|
||||
});
|
||||
|
||||
it("drops a source once every reading from it is deleted", async () => {
|
||||
const { prisma, user, reading } = await seed();
|
||||
const manual = await reading("MANUAL", 0);
|
||||
await reading("APPLE_HEALTH", 1);
|
||||
await prisma.measurement.update({
|
||||
where: { id: manual.id },
|
||||
data: { deletedAt: new Date() },
|
||||
});
|
||||
const { getSourcesInUse } =
|
||||
await import("@/lib/integrations/sources-in-use");
|
||||
expect(await getSourcesInUse(user.id)).toEqual(["APPLE_HEALTH"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user