mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 21:10:59 +00:00
test(anamnesis): pin derived values, AUDIT-C wording and cut-offs, and the per-section prompt gate
This commit is contained in:
1 parent
e5860c9f41
commit
88aa5ccacd
5 files changed
+537
-1
No files matched your search
@@ -169,4 +169,89 @@ describe("profile section access in AI prompt paths", () => {
|
||||
expect(mocks.factFindMany).toHaveBeenCalledOnce();
|
||||
expect(text).toContain("Penicillin");
|
||||
});
|
||||
|
||||
describe("life circumstances (v1.43)", () => {
|
||||
const habitsBytes = new Uint8Array([20]);
|
||||
const goalsBytes = new Uint8Array([21]);
|
||||
const validFrom = new Date("2026-03-01T00:00:00.000Z");
|
||||
|
||||
beforeEach(() => {
|
||||
// Answer with exactly the kinds the query asked for, the way the
|
||||
// database would, so a kind left out of the query is never returned.
|
||||
mocks.factFindMany.mockImplementation(
|
||||
({ where }: { where: { kind: { in: string[] } } }) =>
|
||||
Promise.resolve(
|
||||
[
|
||||
{ kind: "HABITS", valueEncrypted: habitsBytes, validFrom },
|
||||
{ kind: "HEALTH_GOALS", valueEncrypted: goalsBytes, validFrom },
|
||||
].filter((row) => where.kind.in.includes(row.kind)),
|
||||
),
|
||||
);
|
||||
mocks.decrypt.mockImplementation((bytes: Uint8Array) => {
|
||||
if (bytes[0] === 20) {
|
||||
return JSON.stringify({
|
||||
schema: 1,
|
||||
napRegularly: null,
|
||||
snoringReported: true,
|
||||
dietPattern: "vegan",
|
||||
dietSpecifics: [],
|
||||
lateMealsPerWeek: null,
|
||||
fruitVegPortionsPerDay: null,
|
||||
caffeineDrinksPerDay: 4,
|
||||
latestCaffeine: "after_18",
|
||||
});
|
||||
}
|
||||
if (bytes[0] === 21) {
|
||||
return JSON.stringify({ schema: 1, goals: ["sleep"] });
|
||||
}
|
||||
return "unknown";
|
||||
});
|
||||
});
|
||||
|
||||
it("never decrypts a section without its switch", async () => {
|
||||
mockIncluded(["GOALS"]);
|
||||
const text = await getSelfContextTextForUser("user-1", "en");
|
||||
|
||||
const decrypted = mocks.decrypt.mock.calls.map(([bytes]) => bytes);
|
||||
expect(decrypted).not.toContain(habitsBytes);
|
||||
expect(decrypted).toContain(goalsBytes);
|
||||
const query = mocks.factFindMany.mock.calls[0][0] as {
|
||||
where: { kind: { in: string[] } };
|
||||
};
|
||||
expect(query.where.kind.in).not.toContain("HABITS");
|
||||
expect(text).toContain("Health goals: sleep");
|
||||
expect(text).toContain("self-report, valid since 2026-03");
|
||||
expect(text).not.toContain("vegan");
|
||||
});
|
||||
|
||||
it("adds the section once it is switched on", async () => {
|
||||
mockIncluded(["HABITS"]);
|
||||
const text = await getSelfContextTextForUser("user-1", "en");
|
||||
expect(mocks.decrypt.mock.calls.map(([bytes]) => bytes)).toContain(
|
||||
habitsBytes,
|
||||
);
|
||||
expect(text).toContain("Diet pattern: vegan");
|
||||
expect(text).toContain("Latest caffeine: after 18");
|
||||
});
|
||||
|
||||
it("gives AUDIT-C as a sum with its screening verdict under the alcohol switch only", async () => {
|
||||
mocks.auditFindFirst.mockResolvedValue({
|
||||
totalScore: 5,
|
||||
takenAt: new Date("2026-10-01T09:00:00.000Z"),
|
||||
locale: "de",
|
||||
});
|
||||
mocks.userFindUnique.mockResolvedValue({ gender: "MALE", locale: "de" });
|
||||
|
||||
mockIncluded(["ALCOHOL_PATTERN"]);
|
||||
const text = await getSelfContextTextForUser("user-1", "de");
|
||||
expect(text).toContain(
|
||||
"AUDIT-C (Screening, keine Diagnose): 5 von 12 am 2026-10-01, Screening-Grenzwert 5 erreicht",
|
||||
);
|
||||
|
||||
mocks.auditFindFirst.mockClear();
|
||||
mockIncluded(["GOALS"]);
|
||||
await getSelfContextTextForUser("user-1", "de");
|
||||
expect(mocks.auditFindFirst).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -3,7 +3,11 @@ import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
// Mock the DB so the builder is hermetic — we control the rows.
|
||||
const findMany = vi.fn();
|
||||
vi.mock("@/lib/db", () => ({
|
||||
prisma: { measurement: { findMany: (...a: unknown[]) => findMany(...a) } },
|
||||
prisma: {
|
||||
measurement: { findMany: (...a: unknown[]) => findMany(...a) },
|
||||
// v1.43 — the free-weekday answers; none here, so the weekend default.
|
||||
healthProfileFactRevision: { findMany: vi.fn(async () => []) },
|
||||
},
|
||||
}));
|
||||
vi.mock("@/lib/rollups/measurement-read", () => ({
|
||||
loadUserSourcePriority: vi.fn(async () => null),
|
||||
@@ -146,6 +150,21 @@ describe("computeSleepRhythmFromNights window cap", () => {
|
||||
// rhythm route (42-day read) both depend on to render identical values.
|
||||
expect(oneYear.chronotype).toEqual(sixWeeks.chronotype);
|
||||
});
|
||||
|
||||
it("counts the answered free weekdays as free nights instead of the weekend (v1.43)", () => {
|
||||
const nights = trailingNights(42);
|
||||
const weekend = computeSleepRhythmFromNights(nights, 420);
|
||||
const tueWed = computeSleepRhythmFromNights(nights, 420, "COMPUTED", [
|
||||
{
|
||||
validFrom: new Date("2026-01-01T00:00:00.000Z"),
|
||||
validUntil: null,
|
||||
days: [2, 3, 4],
|
||||
},
|
||||
]);
|
||||
expect(weekend.chronotype.freeNightsCounted).toBe(12);
|
||||
// Three free weekdays over six weeks: 18 free nights, not the weekend 12.
|
||||
expect(tueWed.chronotype.freeNightsCounted).toBe(18);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computeAverageSleep", () => {
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
auditCCutoff,
|
||||
auditCInstrument,
|
||||
auditCTotal,
|
||||
isValidatedAuditCLocale,
|
||||
normaliseAuditCLocale,
|
||||
} from "../audit-c";
|
||||
import { AUDIT_C_ITEMS, AUDIT_C_STANDARD_DRINK } from "../instruments";
|
||||
|
||||
describe("AUDIT-C cut-offs", () => {
|
||||
it("German entry, woman, sum 4: at the S3 cut-off of 4", () => {
|
||||
const cutoff = auditCCutoff("de", "FEMALE", "de");
|
||||
expect(cutoff).toMatchObject({
|
||||
value: 4,
|
||||
sex: "female",
|
||||
source: "S3_ALCOHOL_DE",
|
||||
});
|
||||
expect(cutoff?.sourceLabel).toContain("Deutschland");
|
||||
expect(auditCTotal([2, 1, 1]) >= cutoff!.value).toBe(true);
|
||||
});
|
||||
|
||||
it("German men 5, Korean 5 and 4, everyone else Bradley 4 and 3", () => {
|
||||
expect(auditCCutoff("de", "MALE", "de")?.value).toBe(5);
|
||||
expect(auditCCutoff("ko", "MALE", "ko")).toMatchObject({
|
||||
value: 5,
|
||||
source: "KNHANES_KR",
|
||||
});
|
||||
expect(auditCCutoff("ko", "FEMALE", "ko")?.value).toBe(4);
|
||||
expect(auditCCutoff("en", "MALE", "en")).toMatchObject({
|
||||
value: 4,
|
||||
source: "BRADLEY_2007",
|
||||
});
|
||||
expect(auditCCutoff("fr", "FEMALE", "fr")?.value).toBe(3);
|
||||
});
|
||||
|
||||
it("has no cut-off without a binary sex in the profile", () => {
|
||||
expect(auditCCutoff("de", null, "de")).toBeNull();
|
||||
expect(auditCCutoff("de", "OTHER", "de")).toBeNull();
|
||||
});
|
||||
|
||||
it("names the country in the reader's language", () => {
|
||||
expect(auditCCutoff("en", "MALE", "es")?.sourceLabel).toContain(
|
||||
"Estados Unidos",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AUDIT-C instrument", () => {
|
||||
it("serves the validated wording in the account language", () => {
|
||||
const de = auditCInstrument("de");
|
||||
expect(de.locale).toBe("de");
|
||||
expect(de.validatedLocale).toBe(true);
|
||||
expect(de.items).toHaveLength(3);
|
||||
expect(de.items[0].options.map((o) => o.value)).toEqual([0, 1, 2, 3, 4]);
|
||||
expect(de.attribution).toBe(
|
||||
"As a WHO-approved instrument, the AUDIT is in the public domain.",
|
||||
);
|
||||
});
|
||||
|
||||
it("falls back to the WHO English items, never a translation of its own", () => {
|
||||
for (const locale of ["fr", "es", "it"]) {
|
||||
const instrument = auditCInstrument(locale);
|
||||
expect(instrument.locale).toBe("en");
|
||||
expect(instrument.validatedLocale).toBe(false);
|
||||
expect(instrument.items[0].stem).toBe(AUDIT_C_ITEMS.en.items[0].stem);
|
||||
// The help text is still in the account language.
|
||||
expect(instrument.standardDrink).toBe(AUDIT_C_STANDARD_DRINK[locale]);
|
||||
}
|
||||
});
|
||||
|
||||
it("knows which languages are validated", () => {
|
||||
expect(["de", "en", "ko", "pl"].every(isValidatedAuditCLocale)).toBe(true);
|
||||
expect(["es", "fr", "it"].some(isValidatedAuditCLocale)).toBe(false);
|
||||
expect(normaliseAuditCLocale("pt-BR")).toBe("en");
|
||||
expect(normaliseAuditCLocale("de-AT")).toBe("de");
|
||||
});
|
||||
|
||||
it("carries a standard-drink sentence for every app language", () => {
|
||||
for (const locale of ["de", "en", "es", "fr", "it", "pl", "ko"]) {
|
||||
expect(AUDIT_C_STANDARD_DRINK[locale]?.length).toBeGreaterThan(20);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* The validated wording must not change: re-wording invalidates the score,
|
||||
* and the WHO licence forbids material changes. The hashes pin the exact
|
||||
* characters per language; a change here needs a new primary source, and
|
||||
* the source note in `instruments.ts` changes with it.
|
||||
*/
|
||||
it("keeps the validated wording byte for byte", () => {
|
||||
const hashes = Object.fromEntries(
|
||||
Object.entries(AUDIT_C_ITEMS).map(([locale, wording]) => [
|
||||
locale,
|
||||
createHash("sha256")
|
||||
.update(JSON.stringify(wording.items))
|
||||
.digest("hex")
|
||||
.slice(0, 16),
|
||||
]),
|
||||
);
|
||||
expect(hashes).toEqual(PINNED);
|
||||
});
|
||||
});
|
||||
|
||||
const PINNED: Record<string, string> = {
|
||||
de: "4c072c57581dfa60",
|
||||
en: "7d91058682d56051",
|
||||
ko: "5617030281f6f963",
|
||||
pl: "1a06ca242af64e54",
|
||||
};
|
||||
@@ -0,0 +1,205 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
activityMinutesPerWeek,
|
||||
computePackYears,
|
||||
dayTypeForNight,
|
||||
isReviewDue,
|
||||
screeningHints,
|
||||
type ScreeningInputs,
|
||||
} from "../life-derived";
|
||||
import type { TobaccoDetailValue } from "@/lib/validations/life-circumstances";
|
||||
|
||||
const NOW = new Date("2026-10-16T12:00:00.000Z");
|
||||
|
||||
function tobacco(over: Partial<TobaccoDetailValue> = {}): TobaccoDetailValue {
|
||||
return {
|
||||
schema: 1,
|
||||
products: ["cigarettes"],
|
||||
cigarettesPerDay: 20,
|
||||
startYear: 1990,
|
||||
quitMonth: null,
|
||||
secondhandSmoke: null,
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
describe("pack-years", () => {
|
||||
it("20 a day from 1990 to 2020 is 30.0", () => {
|
||||
expect(
|
||||
computePackYears("FORMER", tobacco({ quitMonth: "2020-06" }), NOW),
|
||||
).toEqual({ value: 30, status: "computed" });
|
||||
});
|
||||
|
||||
it("runs to the current year for a current smoker, to one decimal", () => {
|
||||
expect(
|
||||
computePackYears("CURRENT", tobacco({ cigarettesPerDay: 15 }), NOW),
|
||||
).toEqual({ value: 27, status: "computed" });
|
||||
expect(
|
||||
computePackYears(
|
||||
"CURRENT",
|
||||
tobacco({ cigarettesPerDay: 7, startYear: 2010 }),
|
||||
NOW,
|
||||
),
|
||||
).toEqual({ value: 5.6, status: "computed" });
|
||||
});
|
||||
|
||||
it("is missing without a start year, never estimated", () => {
|
||||
expect(
|
||||
computePackYears("CURRENT", tobacco({ startYear: null }), NOW),
|
||||
).toEqual({ value: null, status: "missing_inputs" });
|
||||
expect(computePackYears("CURRENT", null, NOW).status).toBe(
|
||||
"missing_inputs",
|
||||
);
|
||||
});
|
||||
|
||||
it("needs the quit date of a former smoker", () => {
|
||||
expect(computePackYears("FORMER", tobacco(), NOW).status).toBe(
|
||||
"missing_inputs",
|
||||
);
|
||||
});
|
||||
|
||||
it("does not apply to someone who never smoked or smokes no cigarettes", () => {
|
||||
expect(computePackYears("NEVER", tobacco(), NOW).status).toBe(
|
||||
"not_applicable",
|
||||
);
|
||||
expect(
|
||||
computePackYears(
|
||||
"CURRENT",
|
||||
tobacco({ products: ["e_cigarette"], cigarettesPerDay: null }),
|
||||
NOW,
|
||||
).status,
|
||||
).toBe("not_applicable");
|
||||
});
|
||||
});
|
||||
|
||||
describe("activity minutes", () => {
|
||||
it("multiplies the two Exercise Vital Sign answers", () => {
|
||||
expect(activityMinutesPerWeek(5, 30)).toEqual({
|
||||
value: 150,
|
||||
meetsWho150: true,
|
||||
});
|
||||
expect(activityMinutesPerWeek(2, 30)).toEqual({
|
||||
value: 60,
|
||||
meetsWho150: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("stays null with either answer missing", () => {
|
||||
expect(activityMinutesPerWeek(null, 30)).toEqual({
|
||||
value: null,
|
||||
meetsWho150: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("free weekdays per night", () => {
|
||||
// 2026-10-13 is a Tuesday, 2026-10-17 a Saturday.
|
||||
const spans = [
|
||||
{
|
||||
validFrom: new Date("2026-10-01T00:00:00.000Z"),
|
||||
validUntil: null,
|
||||
days: [2, 3],
|
||||
},
|
||||
];
|
||||
|
||||
it("reads a night against the answer in force: Tuesday and Wednesday free", () => {
|
||||
expect(dayTypeForNight("2026-10-13", spans)).toBe("free");
|
||||
expect(dayTypeForNight("2026-10-14", spans)).toBe("free");
|
||||
expect(dayTypeForNight("2026-10-17", spans)).toBe("work");
|
||||
});
|
||||
|
||||
it("keeps the weekend default for nights before the answer", () => {
|
||||
expect(dayTypeForNight("2026-09-26", spans)).toBe("free");
|
||||
expect(dayTypeForNight("2026-09-29", spans)).toBe("work");
|
||||
});
|
||||
|
||||
it("is the weekend default with no answer at all", () => {
|
||||
expect(dayTypeForNight("2026-10-17", [])).toBe("free");
|
||||
expect(dayTypeForNight("2026-10-13", [])).toBe("work");
|
||||
});
|
||||
});
|
||||
|
||||
describe("review due", () => {
|
||||
it("after a year, and never without an answer", () => {
|
||||
expect(isReviewDue(new Date("2025-10-15T00:00:00.000Z"), NOW)).toBe(true);
|
||||
expect(isReviewDue(new Date("2026-01-01T00:00:00.000Z"), NOW)).toBe(false);
|
||||
expect(isReviewDue(null, NOW)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("screening hints", () => {
|
||||
function input(over: Partial<ScreeningInputs> = {}): ScreeningInputs {
|
||||
const detail = over.detail ?? tobacco({ startYear: 1985 });
|
||||
return {
|
||||
ageYears: 60,
|
||||
sex: "MALE",
|
||||
smokingStatus: "CURRENT",
|
||||
detail,
|
||||
packYears: computePackYears(over.smokingStatus ?? "CURRENT", detail, NOW),
|
||||
locale: "de",
|
||||
now: NOW,
|
||||
...over,
|
||||
};
|
||||
}
|
||||
|
||||
it("suggests the German low-dose CT with the G-BA source and country", () => {
|
||||
const [lung] = screeningHints(input());
|
||||
expect(lung.id).toBe("lung_ldct");
|
||||
expect(lung.status).toBe("suggested");
|
||||
expect(lung.region).toBe("DE");
|
||||
expect(lung.sourceLabel).toContain("G-BA");
|
||||
expect(lung.sourceLabel).toContain("Deutschland");
|
||||
expect(lung.sourceUrl).toMatch(/^https:\/\/www\.g-ba\.de\//);
|
||||
});
|
||||
|
||||
it("names the failing criteria", () => {
|
||||
const [lung] = screeningHints(
|
||||
input({
|
||||
ageYears: 45,
|
||||
detail: tobacco({ startYear: 2015, cigarettesPerDay: 10 }),
|
||||
}),
|
||||
);
|
||||
expect(lung.status).toBe("not_eligible");
|
||||
expect(lung.reasonKeys).toEqual(
|
||||
expect.arrayContaining([
|
||||
"age_out_of_range",
|
||||
"pack_years_below_15",
|
||||
"smoked_years_below_25",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("says unknown, not no, when the birth date is missing", () => {
|
||||
const [lung] = screeningHints(input({ ageYears: null }));
|
||||
expect(lung.status).toBe("unknown");
|
||||
expect(lung.reasonKeys).toContain("missing_birth_date");
|
||||
});
|
||||
|
||||
it("uses the US rules outside German, with the country in the account language", () => {
|
||||
const [lung, aaa] = screeningHints(input({ locale: "fr" }));
|
||||
expect(lung.region).toBe("US");
|
||||
expect(lung.sourceLabel).toContain("USPSTF");
|
||||
expect(lung.sourceLabel).toContain("États-Unis");
|
||||
expect(aaa.status).toBe("not_eligible");
|
||||
expect(aaa.reasonKeys).toContain("age_out_of_range");
|
||||
});
|
||||
|
||||
it("offers the aorta ultrasound to every man from 65 in Germany, not to women", () => {
|
||||
expect(screeningHints(input({ ageYears: 66 }))[1].status).toBe("suggested");
|
||||
const women = screeningHints(input({ ageYears: 66, sex: "FEMALE" }))[1];
|
||||
expect(women.status).toBe("not_eligible");
|
||||
expect(women.reasonKeys).toContain("sex_not_male");
|
||||
expect(screeningHints(input({ ageYears: 66, sex: null }))[1].status).toBe(
|
||||
"unknown",
|
||||
);
|
||||
});
|
||||
|
||||
it("asks the US rule for a smoking history", () => {
|
||||
const never = screeningHints(
|
||||
input({ ageYears: 70, locale: "en", smokingStatus: "NEVER" }),
|
||||
);
|
||||
expect(never[0].reasonKeys).toEqual(["never_smoked"]);
|
||||
expect(never[1].reasonKeys).toContain("never_smoked");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,114 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
auditCRequestSchema,
|
||||
factWriteSchema,
|
||||
isStoredFactPlaintext,
|
||||
lifeAiSectionsPatchSchema,
|
||||
} from "../life-circumstances";
|
||||
|
||||
const tobacco = {
|
||||
schema: 1,
|
||||
products: ["cigarettes"],
|
||||
cigarettesPerDay: 20,
|
||||
startYear: 1990,
|
||||
quitMonth: "2020-06",
|
||||
secondhandSmoke: null,
|
||||
};
|
||||
|
||||
describe("fact writes (v1.43)", () => {
|
||||
it("still takes the three closed kinds with their string value", () => {
|
||||
expect(
|
||||
factWriteSchema.safeParse({ kind: "SMOKING_STATUS", value: "FORMER" })
|
||||
.success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
factWriteSchema.safeParse({ kind: "SMOKING_STATUS", value: "ROTATING" })
|
||||
.success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("takes a life kind with its object value and an optional day", () => {
|
||||
const parsed = factWriteSchema.safeParse({
|
||||
kind: "TOBACCO_DETAIL",
|
||||
value: tobacco,
|
||||
validFrom: "2026-03-01",
|
||||
});
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
it("refuses cigarettes per day without cigarettes, and unknown keys", () => {
|
||||
expect(
|
||||
factWriteSchema.safeParse({
|
||||
kind: "TOBACCO_DETAIL",
|
||||
value: { ...tobacco, products: ["e_cigarette"] },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
factWriteSchema.safeParse({
|
||||
kind: "TOBACCO_DETAIL",
|
||||
value: { ...tobacco, packYears: 30 },
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("caps the goals at three and the sitting hours at half-hour steps", () => {
|
||||
expect(
|
||||
factWriteSchema.safeParse({
|
||||
kind: "HEALTH_GOALS",
|
||||
value: {
|
||||
schema: 1,
|
||||
goals: ["sleep", "weight", "fitness", "blood_sugar"],
|
||||
},
|
||||
}).success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
factWriteSchema.safeParse({
|
||||
kind: "ACTIVITY_HABIT",
|
||||
value: {
|
||||
schema: 1,
|
||||
moderateDaysPerWeek: 3,
|
||||
moderateMinutesPerDay: 30,
|
||||
sittingHoursWorkday: 7.25,
|
||||
},
|
||||
}).success,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("checks a backup's plaintext the way a write checks the value", () => {
|
||||
expect(isStoredFactPlaintext("SHIFT_SCHEDULE", "ROTATING")).toBe(true);
|
||||
expect(
|
||||
isStoredFactPlaintext("TOBACCO_DETAIL", JSON.stringify(tobacco)),
|
||||
).toBe(true);
|
||||
expect(isStoredFactPlaintext("TOBACCO_DETAIL", "{")).toBe(false);
|
||||
expect(isStoredFactPlaintext("HABITS", JSON.stringify(tobacco))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AI switches and AUDIT-C requests", () => {
|
||||
it("needs at least one section", () => {
|
||||
expect(lifeAiSectionsPatchSchema.safeParse({ sections: {} }).success).toBe(
|
||||
false,
|
||||
);
|
||||
expect(
|
||||
lifeAiSectionsPatchSchema.safeParse({ sections: { HABITS: true } })
|
||||
.success,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("takes three items from 0 to 4", () => {
|
||||
expect(
|
||||
auditCRequestSchema.safeParse({ items: [0, 4, 2], source: "WEB" })
|
||||
.success,
|
||||
).toBe(true);
|
||||
expect(
|
||||
auditCRequestSchema.safeParse({ items: [0, 5, 2], source: "WEB" })
|
||||
.success,
|
||||
).toBe(false);
|
||||
expect(
|
||||
auditCRequestSchema.safeParse({ items: [0, 1], source: "WEB" }).success,
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user