Просмотр исходного кода

Return null from detail read-getters for missing records (#219)

Deleting a quote, customer or inspection revalidates the current route,
re-rendering the deleted record's page server-side and logging
'not found' errors from getQuote, getCustomer and getInspection. These
read-getters now return null like getServiceRecord and the inventory
part getter already do; the pages render their not-found state, org
scoping and all mutation errors are unchanged.
Bernt Christian Egeland 1 месяц назад
Родитель
Сommit
6cb2d836f3

+ 3 - 3
src/__tests__/multitenancy/customer-inventory-isolation.test.ts

@@ -124,14 +124,14 @@ beforeEach(() => {
 // ---------------------------------------------------------------------------
 
 describe("getCustomer — cross-org isolation", () => {
-  it("returns 'Customer not found' error when requesting another org's customer", async () => {
+  it("returns null (no data) when requesting another org's customer", async () => {
     setupOrgAOwner();
     vi.mocked(db.customer.findFirst).mockResolvedValue(null);
 
     const result = await getCustomer(`${ORG_B}-customer-id`);
 
-    expect(result.success).toBe(false);
-    expect(result.error).toBe("Customer not found");
+    expect(result.success).toBe(true);
+    expect(result.data).toBeNull();
   });
 
   it("returns customer data when requesting the caller's own customer", async () => {

+ 3 - 3
src/__tests__/multitenancy/inspection-isolation.test.ts

@@ -64,14 +64,14 @@ beforeEach(() => {
 });
 
 describe("getInspection — cross-org isolation", () => {
-  it("returns error when requesting another org's inspection", async () => {
+  it("returns null (no data) when requesting another org's inspection", async () => {
     setupOrgAOwner();
     vi.mocked(db.inspection.findFirst).mockResolvedValue(null);
 
     const result = await getInspection(`${ORG_B}-inspection-id`);
 
-    expect(result.success).toBe(false);
-    expect(result.error).toBe("Inspection not found");
+    expect(result.success).toBe(true);
+    expect(result.data).toBeNull();
   });
 
   it("read query is scoped to organizationId", async () => {

+ 6 - 6
src/__tests__/multitenancy/quote-inspection-isolation.test.ts

@@ -118,14 +118,14 @@ beforeEach(() => {
 // ---------------------------------------------------------------------------
 
 describe("getQuote — cross-org isolation", () => {
-  it("returns 'Quote not found' error when requesting another org's quote ID", async () => {
+  it("returns null (no data) when requesting another org's quote ID", async () => {
     setupOrgAOwner();
     vi.mocked(db.quote.findFirst).mockResolvedValue(null);
 
     const result = await getQuote(`${ORG_B}-quote-id`);
 
-    expect(result.success).toBe(false);
-    expect(result.error).toBe("Quote not found");
+    expect(result.success).toBe(true);
+    expect(result.data).toBeNull();
   });
 
   it("returns quote data when requesting the caller's own quote", async () => {
@@ -310,14 +310,14 @@ describe("deleteQuote — cross-org isolation", () => {
 // ---------------------------------------------------------------------------
 
 describe("getInspection — cross-org isolation", () => {
-  it("returns 'Inspection not found' error when requesting another org's inspection", async () => {
+  it("returns null (no data) when requesting another org's inspection", async () => {
     setupOrgAOwner();
     vi.mocked(db.inspection.findFirst).mockResolvedValue(null);
 
     const result = await getInspection(`${ORG_B}-insp-id`);
 
-    expect(result.success).toBe(false);
-    expect(result.error).toBe("Inspection not found");
+    expect(result.success).toBe(true);
+    expect(result.data).toBeNull();
   });
 
   it("returns inspection data when requesting the caller's own inspection", async () => {

+ 3 - 1
src/features/customers/Actions/customerActions.ts

@@ -42,7 +42,9 @@ export async function getCustomer(customerId: string) {
       },
     });
 
-    if (!customer) throw new Error("Customer not found");
+    // Missing or foreign-org customer yields null rather than an error: the
+    // page renders its not-found state, and this also runs during the
+    // post-delete re-render of the customer route.
     return customer;
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.CUSTOMERS }] });
 }

+ 3 - 1
src/features/inspections/Actions/inspectionActions.ts

@@ -98,7 +98,9 @@ export async function getInspection(id: string) {
         },
       },
     });
-    if (!inspection) throw new Error("Inspection not found");
+    // Missing or foreign-org inspection yields null rather than an error: the
+    // page renders its not-found state, and this also runs during the
+    // post-delete re-render of the inspection route.
     return inspection;
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.INSPECTIONS }] });
 }

+ 3 - 1
src/features/quotes/Actions/quoteActions.ts

@@ -110,7 +110,9 @@ export async function getQuote(quoteId: string) {
         },
       },
     });
-    if (!quote) throw new Error("Quote not found");
+    // Missing or foreign-org quote yields null rather than an error: the page
+    // renders its not-found state, and this also runs during the post-delete
+    // re-render of the quote route.
     return quote;
   }, { requiredPermissions: [{ action: PermissionAction.READ, subject: PermissionSubject.QUOTES }] });
 }