Add patient personalization: profile saved once, reused every chat turn

This commit is contained in:
2026-08-25 11:55:13 +07:00
parent 501c226ecd
commit 1ddd878431
12 changed files with 548 additions and 6 deletions
@@ -0,0 +1,99 @@
import "reflect-metadata";
import { Test } from "@nestjs/testing";
import { JwtService } from "@nestjs/jwt";
import { Pool } from "pg";
import { AuthController } from "../src/auth/auth.controller";
import { AuthService } from "../src/auth/auth.service";
import { JwtAuthGuard } from "../src/auth/jwt.guard";
describe("patient profile", () => {
let controller: AuthController;
let service: AuthService;
let pool: { query: jest.Mock };
beforeEach(async () => {
pool = { query: jest.fn() };
const moduleRef = await Test.createTestingModule({
controllers: [AuthController],
providers: [
AuthService,
JwtAuthGuard,
{ provide: Pool, useValue: pool },
{ provide: JwtService, useValue: { signAsync: jest.fn(), verifyAsync: jest.fn() } },
],
}).compile();
controller = moduleRef.get(AuthController);
service = moduleRef.get(AuthService);
});
it("getPatientProfile returns an all-null profile when no row exists", async () => {
pool.query.mockResolvedValueOnce({ rows: [] });
const profile = await service.getPatientProfile("user-1");
expect(profile).toEqual({
ageText: null,
weightKg: null,
renalFunction: null,
hepaticFunction: null,
knownAllergies: null,
});
});
it("getPatientProfile maps the stored row, converting numeric weight from string", async () => {
pool.query.mockResolvedValueOnce({
rows: [
{
age_text: "8 tuổi",
weight_kg: "25.5",
renal_function: null,
hepatic_function: null,
known_allergies: "penicillin",
},
],
});
const profile = await service.getPatientProfile("user-1");
expect(profile.ageText).toBe("8 tuổi");
expect(profile.weightKg).toBe(25.5);
expect(profile.knownAllergies).toBe("penicillin");
});
it("savePatientProfile upserts then re-reads the row (one INSERT..ON CONFLICT, one SELECT)", async () => {
pool.query.mockResolvedValueOnce({ rows: [] }); // the upsert itself
pool.query.mockResolvedValueOnce({
rows: [
{
age_text: "30 tuổi",
weight_kg: "70",
renal_function: null,
hepatic_function: null,
known_allergies: null,
},
],
});
const result = await service.savePatientProfile("user-1", {
ageText: "30 tuổi",
weightKg: 70,
});
expect(pool.query).toHaveBeenCalledTimes(2);
expect(pool.query.mock.calls[0][0]).toMatch(/ON CONFLICT \(user_id\) DO UPDATE/);
expect(result.ageText).toBe("30 tuổi");
expect(result.weightKg).toBe(70);
});
it("controller.getPatientProfile reads the JWT payload's sub, not a request param", async () => {
const spy = jest
.spyOn(service, "getPatientProfile")
.mockResolvedValue({
ageText: null,
weightKg: null,
renalFunction: null,
hepaticFunction: null,
knownAllergies: null,
});
const request = { user: { sub: "user-42", username: "demo", role: "user" } };
await controller.getPatientProfile(request as never);
expect(spy).toHaveBeenCalledWith("user-42");
});
});