From 908e4e724f819930507e3c3dd09168baed171351 Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Sun, 15 Mar 2026 19:27:39 +0100 Subject: [PATCH] fix: remove dead shareStockStatus gating from shared medication overview (#436) The shareStockStatus UI toggle was replaced by shareMedicationOverview in commit e0fb77d, but the backend gating logic was left intact. Users who had previously set shareStockStatus=false were stuck with empty stock values ('-') on the shared medication overview with no UI to change it. - Remove showStockStatus parameter from buildSharedMedicationOverview() - Remove visibility gating that nullified stock fields - Remove shareStockStatus from settings API responses and PUT schema - Remove shareStockStatus from frontend types, hooks, and context - Clean up all related test fixtures and dead test cases - DB column share_stock_status retained (never remove columns) --- backend/src/routes/settings.ts | 9 --- backend/src/routes/share.ts | 4 -- backend/src/services/coverage.ts | 19 ++---- backend/src/test/e2e-routes.test.ts | 11 +--- backend/src/test/routes-real.test.ts | 6 -- .../src/test/settings-auth-security.test.ts | 1 - backend/src/test/settings.test.ts | 63 +------------------ backend/src/test/share.test.ts | 44 ------------- frontend/src/context/AppContext.tsx | 1 - frontend/src/hooks/useSettings.ts | 3 - .../test/components/SharedSchedule.test.tsx | 2 - .../SharedScheduleTodayOnly.test.tsx | 1 - frontend/src/test/context/AppContext.test.tsx | 2 - frontend/src/test/pages/SettingsPage.test.tsx | 1 - frontend/src/types/index.ts | 1 - 15 files changed, 11 insertions(+), 157 deletions(-) diff --git a/backend/src/routes/settings.ts b/backend/src/routes/settings.ts index 42617e8..b3303aa 100644 --- a/backend/src/routes/settings.ts +++ b/backend/src/routes/settings.ts @@ -32,7 +32,6 @@ export type UserSettings = { highStockDays: number; language: Language; stockCalculationMode: "automatic" | "manual"; - shareStockStatus: boolean; shareMedicationOverview: boolean; upcomingTodayOnly: boolean; shareScheduleTodayOnly: boolean; @@ -72,7 +71,6 @@ type SettingsBody = { maxNaggingReminders: number; language: string; stockCalculationMode: "automatic" | "manual"; - shareStockStatus: boolean; shareMedicationOverview: boolean; upcomingTodayOnly: boolean; shareScheduleTodayOnly: boolean; @@ -222,7 +220,6 @@ function getDefaultSettings() { highStockDays: envInt("DEFAULT_HIGH_STOCK_DAYS", 180), language: (process.env.DEFAULT_LANGUAGE as "en" | "de") || "en", stockCalculationMode: (process.env.DEFAULT_STOCK_CALCULATION_MODE as "automatic" | "manual") || "automatic", - shareStockStatus: envBool("DEFAULT_SHARE_STOCK_STATUS", true), shareMedicationOverview: envBool("DEFAULT_SHARE_MEDICATION_OVERVIEW", false), upcomingTodayOnly: envBool("DEFAULT_UPCOMING_TODAY_ONLY", false), shareScheduleTodayOnly: envBool("DEFAULT_SHARE_SCHEDULE_TODAY_ONLY", false), @@ -285,7 +282,6 @@ export async function loadUserSettings(userId: number): Promise { highStockDays: settings.highStockDays, language: settings.language as Language, stockCalculationMode: (settings.stockCalculationMode as "automatic" | "manual") ?? "automatic", - shareStockStatus: settings.shareStockStatus ?? true, shareMedicationOverview: settings.shareMedicationOverview ?? false, upcomingTodayOnly: settings.upcomingTodayOnly ?? false, shareScheduleTodayOnly: settings.shareScheduleTodayOnly ?? false, @@ -330,7 +326,6 @@ export async function getAllUserSettings(): Promise { highStockDays: settings.highStockDays, language: settings.language as Language, stockCalculationMode: (settings.stockCalculationMode as "automatic" | "manual") ?? "automatic", - shareStockStatus: settings.shareStockStatus ?? true, shareMedicationOverview: settings.shareMedicationOverview ?? false, upcomingTodayOnly: settings.upcomingTodayOnly ?? false, shareScheduleTodayOnly: settings.shareScheduleTodayOnly ?? false, @@ -415,7 +410,6 @@ export async function settingsRoutes(app: FastifyInstance) { maxNaggingReminders: settings.maxNaggingReminders ?? 5, language: settings.language, stockCalculationMode: settings.stockCalculationMode ?? "automatic", - shareStockStatus: settings.shareStockStatus ?? true, shareMedicationOverview: settings.shareMedicationOverview ?? false, upcomingTodayOnly: settings.upcomingTodayOnly ?? false, shareScheduleTodayOnly: settings.shareScheduleTodayOnly ?? false, @@ -482,7 +476,6 @@ export async function settingsRoutes(app: FastifyInstance) { maxNaggingReminders: { type: "number" }, language: { type: "string", enum: ["en", "de"] }, stockCalculationMode: { type: "string", enum: ["automatic", "manual"] }, - shareStockStatus: { type: "boolean" }, shareMedicationOverview: { type: "boolean" }, upcomingTodayOnly: { type: "boolean" }, shareScheduleTodayOnly: { type: "boolean" }, @@ -510,7 +503,6 @@ export async function settingsRoutes(app: FastifyInstance) { maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, shareMedicationOverview: false, upcomingTodayOnly: false, shareScheduleTodayOnly: false, @@ -561,7 +553,6 @@ export async function settingsRoutes(app: FastifyInstance) { highStockDays: body.highStockDays ?? 180, language: body.language ?? "en", stockCalculationMode: body.stockCalculationMode ?? "automatic", - shareStockStatus: body.shareStockStatus ?? true, shareMedicationOverview: body.shareMedicationOverview ?? false, upcomingTodayOnly: body.upcomingTodayOnly ?? false, shareScheduleTodayOnly: body.shareScheduleTodayOnly ?? false, diff --git a/backend/src/routes/share.ts b/backend/src/routes/share.ts index 60b45e3..a50e415 100644 --- a/backend/src/routes/share.ts +++ b/backend/src/routes/share.ts @@ -62,7 +62,6 @@ const shareReadResponseSchema = { }, stockThresholds: { type: "object", additionalProperties: { type: "number" } }, stockCalculationMode: { type: "string", enum: ["automatic", "manual"] }, - shareStockStatus: { type: "boolean" }, upcomingTodayOnly: { type: "boolean" }, shareScheduleTodayOnly: { type: "boolean" }, }, @@ -251,7 +250,6 @@ export async function shareRoutes(app: FastifyInstance) { medications: meds, doses: await db.select().from(doseTracking).where(eq(doseTracking.userId, share.userId)), thresholdDays: settings?.lowStockDays ?? 30, - showStockStatus: settings?.shareStockStatus ?? true, }) : null; @@ -270,7 +268,6 @@ export async function shareRoutes(app: FastifyInstance) { expiryWarningDays: settings?.expiryWarningDays ?? 90, }, stockCalculationMode: (settings?.stockCalculationMode as "automatic" | "manual") ?? "automatic", - shareStockStatus: settings?.shareStockStatus ?? true, upcomingTodayOnly: settings?.upcomingTodayOnly ?? false, shareScheduleTodayOnly: settings?.shareScheduleTodayOnly ?? false, }; @@ -344,7 +341,6 @@ export async function shareRoutes(app: FastifyInstance) { medications: meds, doses, thresholdDays: settings?.lowStockDays ?? 30, - showStockStatus: settings?.shareStockStatus ?? true, }); return { diff --git a/backend/src/services/coverage.ts b/backend/src/services/coverage.ts index d68dfb9..9a1959c 100644 --- a/backend/src/services/coverage.ts +++ b/backend/src/services/coverage.ts @@ -149,9 +149,8 @@ export function buildSharedMedicationOverview(options: { medications: MedicationRow[]; doses: DoseRow[]; thresholdDays: number; - showStockStatus?: boolean; }): SharedMedicationOverviewItem[] { - const { medications: medicationRows, doses, thresholdDays, showStockStatus = true } = options; + const { medications: medicationRows, doses, thresholdDays } = options; const dosesByMedication = new Map(); for (const dose of doses) { @@ -189,12 +188,6 @@ export function buildSharedMedicationOverview(options: { const depletionDate = daysLeft === null ? null : toDateOnlyString(new Date(todayDate.getTime() + daysLeft * MS_PER_DAY)); const priority = computeOverviewPriority(currentStock, daysLeft, thresholdDays); - const visibleCurrentStock = showStockStatus ? currentStock : null; - const visibleCapacity = showStockStatus ? capacity : null; - const visibleDaysLeft = showStockStatus ? daysLeft : null; - const visibleDepletionDate = showStockStatus ? depletionDate : null; - const visiblePriority = showStockStatus ? priority : null; - return { name: medication.name, genericName: medication.genericName, @@ -205,12 +198,12 @@ export function buildSharedMedicationOverview(options: { pillsPerBlister: medication.pillsPerBlister, totalPills: medication.totalPills, looseTablets: medication.looseTablets, - currentStock: visibleCurrentStock, - capacity: visibleCapacity, - daysLeft: visibleDaysLeft, + currentStock, + capacity, + daysLeft, nextIntakeDate: computeNextIntakeDate(intakes, todayDateOnly), - depletionDate: visibleDepletionDate, - priority: visiblePriority, + depletionDate, + priority, expiryDate: toNullableDate(medication.expiryDate), medicationStartDate: toNullableDate(medication.medicationStartDate), prescriptionEnabled: medication.prescriptionEnabled ?? false, diff --git a/backend/src/test/e2e-routes.test.ts b/backend/src/test/e2e-routes.test.ts index 6c034b9..9d8f774 100644 --- a/backend/src/test/e2e-routes.test.ts +++ b/backend/src/test/e2e-routes.test.ts @@ -587,7 +587,7 @@ describe("E2E Tests with Real Routes", () => { expect(data.expiredAt).toBeTypeOf("string"); }); - it("should hide stock fields in overview when share_stock_status is disabled", async () => { + it("should always show stock fields in overview regardless of share_stock_status setting", async () => { await createMedication(testClient, userId, "Ibuprofen", ["Daniel"]); const token = "0123456789abcdef"; await createShareToken(testClient, userId, "Daniel", token); @@ -604,11 +604,8 @@ describe("E2E Tests with Real Routes", () => { expect(response.statusCode).toBe(200); const [medication] = response.json().medications; - expect(medication.currentStock).toBeNull(); - expect(medication.capacity).toBeNull(); - expect(medication.daysLeft).toBeNull(); - expect(medication.depletionDate).toBeNull(); - expect(medication.priority).toBeNull(); + expect(medication.currentStock).toBeTypeOf("number"); + expect(medication.capacity).toBeTypeOf("number"); }); }); @@ -2469,7 +2466,6 @@ describe("E2E Tests with Real Routes", () => { maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: false, swapDashboardMainSections: false, @@ -2513,7 +2509,6 @@ describe("E2E Tests with Real Routes", () => { maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: false, swapDashboardMainSections: false, diff --git a/backend/src/test/routes-real.test.ts b/backend/src/test/routes-real.test.ts index 32ab39d..b95342c 100644 --- a/backend/src/test/routes-real.test.ts +++ b/backend/src/test/routes-real.test.ts @@ -140,7 +140,6 @@ describe("Real route coverage: settings/export/report", () => { expect(response.statusCode).toBe(200); const body = response.json(); expect(body.language).toBe("en"); - expect(body.shareStockStatus).toBe(true); expect(body.upcomingTodayOnly).toBe(false); expect(body.shareScheduleTodayOnly).toBe(false); }); @@ -177,7 +176,6 @@ describe("Real route coverage: settings/export/report", () => { maxNaggingReminders: 4, language: "en", stockCalculationMode: "manual", - shareStockStatus: true, upcomingTodayOnly: true, shareScheduleTodayOnly: true, swapDashboardMainSections: true, @@ -238,7 +236,6 @@ describe("Real route coverage: settings/export/report", () => { maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: false, swapDashboardMainSections: false, @@ -453,7 +450,6 @@ describe("Real route coverage: settings/export/report", () => { emailPrescriptionReminders: true, shoutrrrPrescriptionReminders: true, stockCalculationMode: "automatic", - shareStockStatus: true, }) ); }); @@ -509,7 +505,6 @@ describe("Real route coverage: settings/export/report", () => { reminderRepeatIntervalMinutes: 30, maxNaggingReminders: 5, stockCalculationMode: "manual", - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: false, swapDashboardMainSections: false, @@ -556,7 +551,6 @@ describe("Real route coverage: settings/export/report", () => { emailPrescriptionReminders: true, shoutrrrPrescriptionReminders: true, stockCalculationMode: "automatic", - shareStockStatus: true, }), ]) ); diff --git a/backend/src/test/settings-auth-security.test.ts b/backend/src/test/settings-auth-security.test.ts index 04a57a1..f13e9e4 100644 --- a/backend/src/test/settings-auth-security.test.ts +++ b/backend/src/test/settings-auth-security.test.ts @@ -224,7 +224,6 @@ describe("Settings and API key security contracts", () => { maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: false, swapDashboardMainSections: false, diff --git a/backend/src/test/settings.test.ts b/backend/src/test/settings.test.ts index a8c7525..41e10a5 100644 --- a/backend/src/test/settings.test.ts +++ b/backend/src/test/settings.test.ts @@ -51,7 +51,6 @@ async function registerSettingsRoutes(ctx: TestContext) { expiryWarningDays: 90, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, }; } @@ -77,7 +76,6 @@ async function registerSettingsRoutes(ctx: TestContext) { expiryWarningDays: s.expiry_warning_days, language: s.language, stockCalculationMode: s.stock_calculation_mode, - shareStockStatus: Boolean(s.share_stock_status ?? 1), }; }); @@ -104,7 +102,6 @@ async function registerSettingsRoutes(ctx: TestContext) { expiryWarningDays?: number; language?: string; stockCalculationMode?: "automatic" | "manual"; - shareStockStatus?: boolean; }; }>("/settings", async (request, reply) => { const userId = 1; @@ -177,7 +174,7 @@ async function registerSettingsRoutes(ctx: TestContext) { body.expiryWarningDays ?? 90, body.language || "en", body.stockCalculationMode || "automatic", - body.shareStockStatus !== false ? 1 : 0, + 1, ], }); } else { @@ -228,7 +225,7 @@ async function registerSettingsRoutes(ctx: TestContext) { body.expiryWarningDays ?? 90, body.language || "en", body.stockCalculationMode || "automatic", - body.shareStockStatus !== false ? 1 : 0, + 1, userId, ], }); @@ -550,62 +547,6 @@ describe("Settings API", () => { // --------------------------------------------------------------------------- // Share Stock Status - // --------------------------------------------------------------------------- - - describe("Share Stock Status", () => { - it("should default to true (show stock on shared links)", async () => { - const response = await ctx.app.inject({ - method: "GET", - url: "/settings", - }); - - expect(response.statusCode).toBe(200); - expect(response.json().shareStockStatus).toBe(true); - }); - - it("should disable share stock status", async () => { - const response = await ctx.app.inject({ - method: "PUT", - url: "/settings", - payload: { shareStockStatus: false }, - }); - - expect(response.statusCode).toBe(200); - - const getResponse = await ctx.app.inject({ - method: "GET", - url: "/settings", - }); - - expect(getResponse.json().shareStockStatus).toBe(false); - }); - - it("should re-enable share stock status", async () => { - // Disable first - await ctx.app.inject({ - method: "PUT", - url: "/settings", - payload: { shareStockStatus: false }, - }); - - // Re-enable - const response = await ctx.app.inject({ - method: "PUT", - url: "/settings", - payload: { shareStockStatus: true }, - }); - - expect(response.statusCode).toBe(200); - - const getResponse = await ctx.app.inject({ - method: "GET", - url: "/settings", - }); - - expect(getResponse.json().shareStockStatus).toBe(true); - }); - }); - // --------------------------------------------------------------------------- // Repeat Reminders & Skip Reminders Settings // --------------------------------------------------------------------------- diff --git a/backend/src/test/share.test.ts b/backend/src/test/share.test.ts index 4fa5764..e1b01ab 100644 --- a/backend/src/test/share.test.ts +++ b/backend/src/test/share.test.ts @@ -142,14 +142,6 @@ async function registerShareRoutes(ctx: TestContext) { const lowStockDays = settingsResult.rows.length > 0 ? (settingsResult.rows[0].low_stock_days as number) : 30; - // Get shareStockStatus setting - const shareStockResult = await client.execute({ - sql: `SELECT share_stock_status FROM user_settings WHERE user_id = ?`, - args: [share.user_id], - }); - const shareStockStatus = - shareStockResult.rows.length > 0 ? Boolean(shareStockResult.rows[0].share_stock_status ?? 1) : true; - return { takenBy: share.taken_by, sharedBy: share.owner_username, @@ -158,7 +150,6 @@ async function registerShareRoutes(ctx: TestContext) { stockThresholds: { lowStockDays, }, - shareStockStatus, }; }); @@ -431,41 +422,6 @@ describe("Share Link API", () => { expect(med.blisters).toHaveLength(1); expect(med.blisters[0].usage).toBe(1); expect(med.blisters[0].every).toBe(1); - - // shareStockStatus should default to true - expect(data.shareStockStatus).toBe(true); - }); - - it("should respect shareStockStatus setting when disabled", async () => { - // Create medication - await createTestMedication(ctx.client, { - userId, - name: "TestMed", - takenBy: ["Daniel"], - packCount: 1, - blistersPerPack: 1, - pillsPerBlister: 10, - looseTablets: 0, - blisters: [{ usage: 1, every: 1, start: "2025-01-01T08:00:00.000Z" }], - }); - - // Set shareStockStatus to false - await setUserSettings(ctx.client, { userId, shareStockStatus: false }); - - // Create share token - const token = await createTestShareToken(ctx.client, { - userId, - takenBy: "Daniel", - scheduleDays: 30, - }); - - const response = await ctx.app.inject({ - method: "GET", - url: `/share/${token}`, - }); - - expect(response.statusCode).toBe(200); - expect(response.json().shareStockStatus).toBe(false); }); it("should return 404 for invalid token", async () => { diff --git a/frontend/src/context/AppContext.tsx b/frontend/src/context/AppContext.tsx index f5dd81a..8368551 100644 --- a/frontend/src/context/AppContext.tsx +++ b/frontend/src/context/AppContext.tsx @@ -792,7 +792,6 @@ export function AppProvider({ children }: { children: React.ReactNode }) { settings.reminderRepeatIntervalMinutes !== savedSettings.reminderRepeatIntervalMinutes || settings.maxNaggingReminders !== savedSettings.maxNaggingReminders || settings.stockCalculationMode !== savedSettings.stockCalculationMode || - settings.shareStockStatus !== savedSettings.shareStockStatus || settings.shareMedicationOverview !== savedSettings.shareMedicationOverview || settings.upcomingTodayOnly !== savedSettings.upcomingTodayOnly || settings.shareScheduleTodayOnly !== savedSettings.shareScheduleTodayOnly || diff --git a/frontend/src/hooks/useSettings.ts b/frontend/src/hooks/useSettings.ts index cf59c7d..41e69b6 100644 --- a/frontend/src/hooks/useSettings.ts +++ b/frontend/src/hooks/useSettings.ts @@ -46,7 +46,6 @@ export interface Settings { shoutrrrIntakeReminders: boolean; shoutrrrPrescriptionReminders: boolean; stockCalculationMode: "automatic" | "manual"; - shareStockStatus: boolean; shareMedicationOverview: boolean; upcomingTodayOnly: boolean; shareScheduleTodayOnly: boolean; @@ -98,7 +97,6 @@ const defaultSettings: Settings = { shoutrrrIntakeReminders: true, shoutrrrPrescriptionReminders: true, stockCalculationMode: "automatic", - shareStockStatus: true, shareMedicationOverview: false, upcomingTodayOnly: false, shareScheduleTodayOnly: false, @@ -258,7 +256,6 @@ export function useSettings(): UseSettingsReturn { shoutrrrIntakeReminders: settingsToSave.shoutrrrIntakeReminders, shoutrrrPrescriptionReminders: settingsToSave.shoutrrrPrescriptionReminders, stockCalculationMode: settingsToSave.stockCalculationMode, - shareStockStatus: settingsToSave.shareStockStatus, shareMedicationOverview: settingsToSave.shareMedicationOverview, upcomingTodayOnly: settingsToSave.upcomingTodayOnly, shareScheduleTodayOnly: settingsToSave.shareScheduleTodayOnly, diff --git a/frontend/src/test/components/SharedSchedule.test.tsx b/frontend/src/test/components/SharedSchedule.test.tsx index 3881349..d16a629 100644 --- a/frontend/src/test/components/SharedSchedule.test.tsx +++ b/frontend/src/test/components/SharedSchedule.test.tsx @@ -18,7 +18,6 @@ function createSharedData() { sharedBy: "Owner", takenBy: "Max", scheduleDays: 30, - shareStockStatus: true, medications: [], }; } @@ -64,7 +63,6 @@ function createSharedDataWithTodayDose() { takenBy: "Max", scheduleDays: 30, automaticDoseId: `1-0-${dateOnlyMs}`, - shareStockStatus: true, medications: [ { id: 1, diff --git a/frontend/src/test/components/SharedScheduleTodayOnly.test.tsx b/frontend/src/test/components/SharedScheduleTodayOnly.test.tsx index ca3abfb..16a948a 100644 --- a/frontend/src/test/components/SharedScheduleTodayOnly.test.tsx +++ b/frontend/src/test/components/SharedScheduleTodayOnly.test.tsx @@ -22,7 +22,6 @@ function createSharedData(overrides: Record = {}) { sharedBy: "Owner", takenBy: "Max", scheduleDays: 30, - shareStockStatus: true, upcomingTodayOnly: false, shareScheduleTodayOnly: true, stockCalculationMode: "automatic", diff --git a/frontend/src/test/context/AppContext.test.tsx b/frontend/src/test/context/AppContext.test.tsx index 48bb4ba..fc7594d 100644 --- a/frontend/src/test/context/AppContext.test.tsx +++ b/frontend/src/test/context/AppContext.test.tsx @@ -131,7 +131,6 @@ describe("useAppContext", () => { shoutrrrStockReminders: true, shoutrrrIntakeReminders: true, stockCalculationMode: "automatic", - shareStockStatus: true, shareMedicationOverview: false, expiryWarningDays: 30, }, @@ -171,7 +170,6 @@ describe("useAppContext", () => { shoutrrrStockReminders: true, shoutrrrIntakeReminders: true, stockCalculationMode: "automatic", - shareStockStatus: true, shareMedicationOverview: false, expiryWarningDays: 30, }, diff --git a/frontend/src/test/pages/SettingsPage.test.tsx b/frontend/src/test/pages/SettingsPage.test.tsx index 07d5df8..be3b4ad 100644 --- a/frontend/src/test/pages/SettingsPage.test.tsx +++ b/frontend/src/test/pages/SettingsPage.test.tsx @@ -43,7 +43,6 @@ const createMockContext = (overrides = {}) => ({ maxNaggingReminders: 5, language: "en", stockCalculationMode: "automatic", - shareStockStatus: true, smtpHost: "", smtpPort: 587, smtpUser: "", diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 892e7dc..ff7d6c1 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -253,7 +253,6 @@ export type SharedScheduleData = { expiryWarningDays?: number; }; stockCalculationMode?: "automatic" | "manual"; - shareStockStatus?: boolean; shareMedicationOverview?: boolean; medicationOverview?: SharedMedicationOverviewItem[] | null; upcomingTodayOnly?: boolean;