2026-02-20 15:28:41 -05:00
|
|
|
package db_test
|
|
|
|
|
|
|
|
|
|
// clinical_test.go — tests for clinical resource persistence (Observation,
|
|
|
|
|
// Condition, DocumentReference, PatientSync).
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"github.com/AmanTahiliani/FHIR-Sandbox/app/models"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
|
|
|
|
testEHRURL = "https://ehr.example.com/fhir"
|
|
|
|
|
testPatientID = "patient-clinical-001"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Observation tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertObservation_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
qty := 98.6
|
|
|
|
|
obs := &models.Observation{
|
|
|
|
|
FHIRID: "obs-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "final",
|
|
|
|
|
Category: "vital-signs",
|
|
|
|
|
CodeText: "Body Temperature",
|
|
|
|
|
CodeSystem: "http://loinc.org",
|
|
|
|
|
CodeCode: "8310-5",
|
|
|
|
|
EffectiveDate: "2024-01-15",
|
|
|
|
|
ValueQuantity: &qty,
|
|
|
|
|
ValueUnit: "°F",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertObservation(obs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertObservation: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update: change value and status.
|
|
|
|
|
newQty := 99.1
|
|
|
|
|
obs.ValueQuantity = &newQty
|
|
|
|
|
obs.Status = "amended"
|
|
|
|
|
id2, err := store.UpsertObservation(obs)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertObservation: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ID must be stable across upserts.
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read back and confirm updated fields.
|
|
|
|
|
rows, err := store.ListObservations(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListObservations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 observation, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
got := rows[0]
|
|
|
|
|
if got.Status != "amended" {
|
|
|
|
|
t.Errorf("Status: got %q, want %q", got.Status, "amended")
|
|
|
|
|
}
|
|
|
|
|
if got.ValueQuantity == nil || *got.ValueQuantity != 99.1 {
|
|
|
|
|
t.Errorf("ValueQuantity: got %v, want 99.1", got.ValueQuantity)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpsertObservation_TenantIsolation(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
makeObs := func(ehrURL string) *models.Observation {
|
|
|
|
|
return &models.Observation{
|
|
|
|
|
FHIRID: "obs-shared",
|
|
|
|
|
EHRURL: ehrURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "final",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertObservation(makeObs("https://ehr-a.example.com/fhir"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("upsert A: %v", err)
|
|
|
|
|
}
|
|
|
|
|
id2, err := store.UpsertObservation(makeObs("https://ehr-b.example.com/fhir"))
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("upsert B: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == id2 {
|
|
|
|
|
t.Error("expected different IDs for same fhir_id at different ehr_urls")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListObservations_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListObservations("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListObservations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0 rows, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListObservations_OrderedNewestFirst(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
for _, item := range []struct {
|
|
|
|
|
id string
|
|
|
|
|
date string
|
|
|
|
|
}{
|
|
|
|
|
{"obs-a", "2024-01-01"},
|
|
|
|
|
{"obs-b", "2024-06-15"},
|
|
|
|
|
{"obs-c", "2023-12-31"},
|
|
|
|
|
} {
|
|
|
|
|
_, err := store.UpsertObservation(&models.Observation{
|
|
|
|
|
FHIRID: item.id,
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "final",
|
|
|
|
|
EffectiveDate: item.date,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertObservation %s: %v", item.id, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListObservations(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListObservations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 3 {
|
|
|
|
|
t.Fatalf("expected 3 rows, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
// Newest first: obs-b (Jun) > obs-a (Jan) > obs-c (Dec 2023)
|
|
|
|
|
if rows[0].FHIRID != "obs-b" {
|
|
|
|
|
t.Errorf("first row: got %q, want obs-b", rows[0].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
if rows[2].FHIRID != "obs-c" {
|
|
|
|
|
t.Errorf("last row: got %q, want obs-c", rows[2].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Condition tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertCondition_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
cond := &models.Condition{
|
|
|
|
|
FHIRID: "cond-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
ClinicalStatus: "active",
|
|
|
|
|
VerificationStatus: "confirmed",
|
|
|
|
|
Category: "problem-list-item",
|
|
|
|
|
CodeText: "Hypertension",
|
|
|
|
|
CodeSystem: "http://snomed.info/sct",
|
|
|
|
|
CodeCode: "38341003",
|
|
|
|
|
OnsetDate: "2020-03-01",
|
|
|
|
|
RecordedDate: "2020-03-05",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertCondition(cond)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertCondition: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update clinical status to resolved.
|
|
|
|
|
cond.ClinicalStatus = "resolved"
|
|
|
|
|
id2, err := store.UpsertCondition(cond)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertCondition: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListConditions(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListConditions: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 condition, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].ClinicalStatus != "resolved" {
|
|
|
|
|
t.Errorf("ClinicalStatus: got %q, want resolved", rows[0].ClinicalStatus)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListConditions_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListConditions("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListConditions: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// DocumentReference tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertDocumentReference_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
doc := &models.DocumentReference{
|
|
|
|
|
FHIRID: "doc-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "current",
|
|
|
|
|
DocStatus: "final",
|
|
|
|
|
TypeText: "Discharge Summary",
|
|
|
|
|
TypeSystem: "http://loinc.org",
|
|
|
|
|
TypeCode: "18842-5",
|
|
|
|
|
Date: "2024-02-10",
|
|
|
|
|
Description: "Hospital discharge summary",
|
|
|
|
|
ContentType: "text/plain",
|
|
|
|
|
ContentURL: "https://ehr.example.com/fhir/Binary/bin-001",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertDocumentReference(doc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertDocumentReference: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Supersede the document.
|
|
|
|
|
doc.Status = "superseded"
|
|
|
|
|
id2, err := store.UpsertDocumentReference(doc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertDocumentReference: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListDocumentReferences(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListDocumentReferences: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 doc, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].Status != "superseded" {
|
|
|
|
|
t.Errorf("Status: got %q, want superseded", rows[0].Status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListDocumentReferences_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListDocumentReferences("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListDocumentReferences: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-02-20 23:59:46 -05:00
|
|
|
// MedicationRequest tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertMedicationRequest_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
med := &models.MedicationRequest{
|
|
|
|
|
FHIRID: "med-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "active",
|
|
|
|
|
Intent: "order",
|
|
|
|
|
MedCodeText: "Metformin 500mg",
|
|
|
|
|
MedCodeSystem: "http://www.nlm.nih.gov/research/umls/rxnorm",
|
|
|
|
|
MedCodeCode: "860975",
|
|
|
|
|
AuthoredOn: "2024-01-10",
|
|
|
|
|
RequesterDisplay: "Dr. Smith",
|
|
|
|
|
DosageText: "1 tablet twice daily",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertMedicationRequest(med)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertMedicationRequest: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
med.Status = "stopped"
|
|
|
|
|
id2, err := store.UpsertMedicationRequest(med)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertMedicationRequest: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListMedicationRequests(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListMedicationRequests: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 med, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].Status != "stopped" {
|
|
|
|
|
t.Errorf("Status: got %q, want stopped", rows[0].Status)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListMedicationRequests_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListMedicationRequests("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListMedicationRequests: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// AllergyIntolerance tests (including reaction fields)
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertAllergyIntolerance_WithReaction(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
allergy := &models.AllergyIntolerance{
|
|
|
|
|
FHIRID: "allergy-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
ClinicalStatus: "active",
|
|
|
|
|
VerificationStatus: "confirmed",
|
|
|
|
|
Type: "allergy",
|
|
|
|
|
Category: "medication",
|
|
|
|
|
Criticality: "high",
|
|
|
|
|
CodeText: "Penicillin",
|
|
|
|
|
CodeSystem: "http://www.nlm.nih.gov/research/umls/rxnorm",
|
|
|
|
|
CodeCode: "7980",
|
|
|
|
|
RecordedDate: "2018-05-01",
|
|
|
|
|
ReactionSeverity: "severe",
|
|
|
|
|
ReactionManifestation: "Anaphylaxis",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertAllergyIntolerance(allergy)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertAllergyIntolerance: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update reaction.
|
|
|
|
|
allergy.ReactionSeverity = "moderate"
|
|
|
|
|
allergy.ReactionManifestation = "Rash"
|
|
|
|
|
id2, err := store.UpsertAllergyIntolerance(allergy)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertAllergyIntolerance: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListAllergyIntolerances(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListAllergyIntolerances: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 allergy, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
got := rows[0]
|
|
|
|
|
if got.ReactionSeverity != "moderate" {
|
|
|
|
|
t.Errorf("ReactionSeverity: got %q, want moderate", got.ReactionSeverity)
|
|
|
|
|
}
|
|
|
|
|
if got.ReactionManifestation != "Rash" {
|
|
|
|
|
t.Errorf("ReactionManifestation: got %q, want Rash", got.ReactionManifestation)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestUpsertAllergyIntolerance_NoReaction(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
allergy := &models.AllergyIntolerance{
|
|
|
|
|
FHIRID: "allergy-no-rxn",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
ClinicalStatus: "active",
|
|
|
|
|
CodeText: "Latex",
|
|
|
|
|
}
|
|
|
|
|
_, err := store.UpsertAllergyIntolerance(allergy)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertAllergyIntolerance (no reaction): %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListAllergyIntolerances(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListAllergyIntolerances: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].ReactionSeverity != "" {
|
|
|
|
|
t.Errorf("expected empty ReactionSeverity, got %q", rows[0].ReactionSeverity)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Immunization tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertImmunization_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
imm := &models.Immunization{
|
|
|
|
|
FHIRID: "imm-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "completed",
|
|
|
|
|
VaccineText: "Influenza, seasonal",
|
|
|
|
|
VaccineSystem: "http://hl7.org/fhir/sid/cvx",
|
|
|
|
|
VaccineCode: "141",
|
|
|
|
|
OccurrenceDate: "2023-10-01",
|
|
|
|
|
PrimarySource: true,
|
|
|
|
|
LotNumber: "LOT123",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertImmunization(imm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertImmunization: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
imm.LotNumber = "LOT456"
|
|
|
|
|
id2, err := store.UpsertImmunization(imm)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertImmunization: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListImmunizations(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListImmunizations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 immunization, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].LotNumber != "LOT456" {
|
|
|
|
|
t.Errorf("LotNumber: got %q, want LOT456", rows[0].LotNumber)
|
|
|
|
|
}
|
|
|
|
|
if !rows[0].PrimarySource {
|
|
|
|
|
t.Error("PrimarySource should be true")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListImmunizations_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListImmunizations("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListImmunizations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListImmunizations_OrderedNewestFirst(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
for _, item := range []struct {
|
|
|
|
|
id string
|
|
|
|
|
date string
|
|
|
|
|
}{
|
|
|
|
|
{"imm-a", "2022-09-01"},
|
|
|
|
|
{"imm-b", "2023-10-15"},
|
|
|
|
|
{"imm-c", "2021-03-01"},
|
|
|
|
|
} {
|
|
|
|
|
_, err := store.UpsertImmunization(&models.Immunization{
|
|
|
|
|
FHIRID: item.id,
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "completed",
|
|
|
|
|
OccurrenceDate: item.date,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertImmunization %s: %v", item.id, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListImmunizations(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListImmunizations: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 3 {
|
|
|
|
|
t.Fatalf("expected 3, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].FHIRID != "imm-b" {
|
|
|
|
|
t.Errorf("first: got %q, want imm-b", rows[0].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
if rows[2].FHIRID != "imm-c" {
|
|
|
|
|
t.Errorf("last: got %q, want imm-c", rows[2].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Procedure tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertProcedure_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
proc := &models.Procedure{
|
|
|
|
|
FHIRID: "proc-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "completed",
|
|
|
|
|
CodeText: "Appendectomy",
|
|
|
|
|
CodeSystem: "http://snomed.info/sct",
|
|
|
|
|
CodeCode: "80146002",
|
|
|
|
|
PerformedDate: "2019-06-15",
|
|
|
|
|
ReasonText: "Acute appendicitis",
|
|
|
|
|
Outcome: "Successful procedure",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertProcedure(proc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertProcedure: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proc.Outcome = "Procedure completed without complications"
|
|
|
|
|
id2, err := store.UpsertProcedure(proc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertProcedure: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListProcedures(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListProcedures: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 procedure, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].Outcome != "Procedure completed without complications" {
|
|
|
|
|
t.Errorf("Outcome: got %q", rows[0].Outcome)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListProcedures_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListProcedures("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListProcedures: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// Encounter tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestUpsertEncounter_NewAndUpdate(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
enc := &models.Encounter{
|
|
|
|
|
FHIRID: "enc-001",
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "finished",
|
|
|
|
|
Class: "AMB",
|
|
|
|
|
TypeText: "Office visit",
|
|
|
|
|
PeriodStart: "2024-03-10",
|
|
|
|
|
PeriodEnd: "2024-03-10",
|
|
|
|
|
ReasonText: "Annual physical",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
id1, err := store.UpsertEncounter(enc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("initial UpsertEncounter: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enc.Status = "cancelled"
|
|
|
|
|
id2, err := store.UpsertEncounter(enc)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("update UpsertEncounter: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if id1 != id2 {
|
|
|
|
|
t.Errorf("ID changed on upsert: was %q, got %q", id1, id2)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListEncounters(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListEncounters: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 1 {
|
|
|
|
|
t.Fatalf("expected 1 encounter, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].Status != "cancelled" {
|
|
|
|
|
t.Errorf("Status: got %q, want cancelled", rows[0].Status)
|
|
|
|
|
}
|
|
|
|
|
if rows[0].Class != "AMB" {
|
|
|
|
|
t.Errorf("Class: got %q, want AMB", rows[0].Class)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListEncounters_Empty(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
rows, err := store.ListEncounters("no-such-patient", testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListEncounters: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 0 {
|
|
|
|
|
t.Errorf("expected 0, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestListEncounters_OrderedNewestFirst(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
for _, item := range []struct {
|
|
|
|
|
id string
|
|
|
|
|
start string
|
|
|
|
|
}{
|
|
|
|
|
{"enc-a", "2023-01-01"},
|
|
|
|
|
{"enc-b", "2024-06-01"},
|
|
|
|
|
{"enc-c", "2022-12-01"},
|
|
|
|
|
} {
|
|
|
|
|
_, err := store.UpsertEncounter(&models.Encounter{
|
|
|
|
|
FHIRID: item.id,
|
|
|
|
|
EHRURL: testEHRURL,
|
|
|
|
|
PatientFHIRID: testPatientID,
|
|
|
|
|
Status: "finished",
|
|
|
|
|
PeriodStart: item.start,
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("UpsertEncounter %s: %v", item.id, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rows, err := store.ListEncounters(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("ListEncounters: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if len(rows) != 3 {
|
|
|
|
|
t.Fatalf("expected 3, got %d", len(rows))
|
|
|
|
|
}
|
|
|
|
|
if rows[0].FHIRID != "enc-b" {
|
|
|
|
|
t.Errorf("first: got %q, want enc-b", rows[0].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
if rows[2].FHIRID != "enc-c" {
|
|
|
|
|
t.Errorf("last: got %q, want enc-c", rows[2].FHIRID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
2026-02-20 15:28:41 -05:00
|
|
|
// PatientSync tests
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
func TestRecordAndLatestSync(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
// No sync yet.
|
|
|
|
|
ps, err := store.LatestSync(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("LatestSync (empty): %v", err)
|
|
|
|
|
}
|
|
|
|
|
if ps != nil {
|
|
|
|
|
t.Errorf("expected nil before any sync, got %+v", ps)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record first sync.
|
|
|
|
|
recorded, err := store.RecordSync(testPatientID, testEHRURL, 10, 3, 1)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("RecordSync: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if recorded.ID == "" {
|
|
|
|
|
t.Fatal("expected non-empty ID")
|
|
|
|
|
}
|
|
|
|
|
if recorded.ObsCount != 10 || recorded.CondCount != 3 || recorded.DocCount != 1 {
|
|
|
|
|
t.Errorf("counts wrong: %+v", recorded)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LatestSync should now return that record.
|
|
|
|
|
latest, err := store.LatestSync(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("LatestSync: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if latest == nil {
|
|
|
|
|
t.Fatal("expected non-nil latest sync")
|
|
|
|
|
}
|
|
|
|
|
if latest.ID != recorded.ID {
|
|
|
|
|
t.Errorf("ID mismatch: got %q, want %q", latest.ID, recorded.ID)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record a second sync with higher counts; LatestSync must return the newer one.
|
|
|
|
|
_, err = store.RecordSync(testPatientID, testEHRURL, 20, 5, 2)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("RecordSync second: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
latest2, err := store.LatestSync(testPatientID, testEHRURL)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("LatestSync second: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if latest2.ObsCount != 20 {
|
|
|
|
|
t.Errorf("expected ObsCount 20 from latest, got %d", latest2.ObsCount)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLatestSync_TenantIsolation(t *testing.T) {
|
|
|
|
|
store := newTestStore(t)
|
|
|
|
|
|
|
|
|
|
_, err := store.RecordSync(testPatientID, "https://ehr-a.example.com/fhir", 5, 1, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("RecordSync EHR-A: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Querying for a different EHR URL should return nil.
|
|
|
|
|
ps, err := store.LatestSync(testPatientID, "https://ehr-b.example.com/fhir")
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("LatestSync EHR-B: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if ps != nil {
|
|
|
|
|
t.Errorf("expected nil for different EHR URL, got %+v", ps)
|
|
|
|
|
}
|
|
|
|
|
}
|