refactor: update API response handling across multiple queries to ensure compliance with ADR-028
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 16m53s
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 16m53s
- Removed direct return of json.data in favor of structured error handling. - Implemented checks for success and data array in useActivityLogQuery, useBestSalePricesQuery, useBrandsQuery, useCategoriesQuery, useFlyerItemsForFlyersQuery, useFlyerItemsQuery, useFlyersQuery, useLeaderboardQuery, useMasterItemsQuery, usePriceHistoryQuery, useShoppingListsQuery, useSuggestedCorrectionsQuery, and useWatchedItemsQuery. - Updated unit tests to reflect changes in expected behavior when API response does not conform to the expected structure. - Updated package.json to use the latest version of @sentry/vite-plugin. - Adjusted vite.config.ts for local development SSL configuration. - Added self-signed SSL certificate and key for local development.
This commit is contained in:
@@ -34,8 +34,12 @@ export const useActivityLogQuery = (limit: number = 20, offset: number = 0) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
// Activity log changes frequently, keep stale time short
|
||||
staleTime: 1000 * 30, // 30 seconds
|
||||
|
||||
@@ -32,8 +32,12 @@ export const useBestSalePricesQuery = (enabled: boolean = true) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled,
|
||||
// Prices update when flyers change, keep fresh for 2 minutes
|
||||
|
||||
@@ -28,8 +28,12 @@ export const useBrandsQuery = (enabled: boolean = true) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled,
|
||||
staleTime: 1000 * 60 * 5, // 5 minutes - brands don't change frequently
|
||||
|
||||
@@ -27,8 +27,12 @@ export const useCategoriesQuery = () => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
staleTime: 1000 * 60 * 60, // 1 hour - categories rarely change
|
||||
});
|
||||
|
||||
@@ -38,8 +38,12 @@ export const useFlyerItemsForFlyersQuery = (flyerIds: number[], enabled: boolean
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled: enabled && flyerIds.length > 0,
|
||||
// Flyer items don't change frequently once created
|
||||
|
||||
@@ -117,9 +117,9 @@ describe('useFlyerItemsQuery', () => {
|
||||
expect(result.current.data).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle response without data property (fallback)', async () => {
|
||||
// Edge case: API returns unexpected format without data property
|
||||
// The hook falls back to returning the raw json object
|
||||
it('should return empty array when response lacks success/data structure (ADR-028)', async () => {
|
||||
// ADR-028: API must return { success: true, data: [...] }
|
||||
// Non-compliant responses return empty array to prevent .map() errors
|
||||
const legacyItems = [{ item_id: 1, name: 'Legacy Item' }];
|
||||
mockedApiClient.fetchFlyerItems.mockResolvedValue({
|
||||
ok: true,
|
||||
@@ -130,7 +130,7 @@ describe('useFlyerItemsQuery', () => {
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
|
||||
// Falls back to raw response when .data is undefined
|
||||
expect(result.current.data).toEqual(legacyItems);
|
||||
// Returns empty array when response doesn't match ADR-028 format
|
||||
expect(result.current.data).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,8 +36,12 @@ export const useFlyerItemsQuery = (flyerId: number | undefined) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
// Only run the query if we have a valid flyer ID
|
||||
enabled: !!flyerId,
|
||||
|
||||
@@ -33,8 +33,12 @@ export const useFlyersQuery = (limit: number = 20, offset: number = 0) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
// Keep data fresh for 2 minutes since flyers don't change frequently
|
||||
staleTime: 1000 * 60 * 2,
|
||||
|
||||
@@ -30,8 +30,12 @@ export const useLeaderboardQuery = (limit: number = 10, enabled: boolean = true)
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled,
|
||||
staleTime: 1000 * 60 * 2, // 2 minutes - leaderboard can change moderately
|
||||
|
||||
@@ -32,8 +32,12 @@ export const useMasterItemsQuery = () => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
// Master items change infrequently, keep data fresh for 10 minutes
|
||||
staleTime: 1000 * 60 * 10,
|
||||
|
||||
@@ -35,8 +35,12 @@ export const usePriceHistoryQuery = (masterItemIds: number[], enabled: boolean =
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled: enabled && masterItemIds.length > 0,
|
||||
staleTime: 1000 * 60 * 10, // 10 minutes - historical data doesn't change frequently
|
||||
|
||||
@@ -32,8 +32,12 @@ export const useShoppingListsQuery = (enabled: boolean) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled,
|
||||
// Keep data fresh for 1 minute since users actively manage shopping lists
|
||||
|
||||
@@ -27,8 +27,12 @@ export const useSuggestedCorrectionsQuery = () => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
staleTime: 1000 * 60, // 1 minute - corrections change moderately
|
||||
});
|
||||
|
||||
@@ -32,8 +32,12 @@ export const useWatchedItemsQuery = (enabled: boolean) => {
|
||||
}
|
||||
|
||||
const json = await response.json();
|
||||
// API returns { success: true, data: [...] }, extract the data array
|
||||
return json.data ?? json;
|
||||
// ADR-028: API returns { success: true, data: [...] }
|
||||
// If success is false or data is not an array, return empty array to prevent .map() errors
|
||||
if (!json.success || !Array.isArray(json.data)) {
|
||||
return [];
|
||||
}
|
||||
return json.data;
|
||||
},
|
||||
enabled,
|
||||
// Keep data fresh for 1 minute since users actively manage watched items
|
||||
|
||||
Reference in New Issue
Block a user