فهرست منبع

fix(frontend): refresh subscription settings after save (#6287)

The derived defaults query is cached indefinitely, so subscription links kept using the old path after settings saves. Invalidate it only after successful saves so inbounds and clients refetch generated subscription URLs.
dawn 9 ساعت پیش
والد
کامیت
b73ceae081
2فایلهای تغییر یافته به همراه62 افزوده شده و 0 حذف شده
  1. 1 0
      frontend/src/api/queries/useAllSettings.ts
  2. 61 0
      frontend/src/test/use-all-settings.test.tsx

+ 1 - 0
frontend/src/api/queries/useAllSettings.ts

@@ -77,6 +77,7 @@ export function useAllSettings() {
       if (!msg?.success) return;
       if (!msg?.success) return;
       if (saved) markSaved(saved);
       if (saved) markSaved(saved);
       queryClient.invalidateQueries({ queryKey: keys.settings.all() });
       queryClient.invalidateQueries({ queryKey: keys.settings.all() });
+      queryClient.invalidateQueries({ queryKey: keys.settings.defaults() });
     },
     },
   });
   });
 
 

+ 61 - 0
frontend/src/test/use-all-settings.test.tsx

@@ -13,6 +13,67 @@ afterEach(() => {
 });
 });
 
 
 describe('useAllSettings', () => {
 describe('useAllSettings', () => {
+  it('refreshes cached default settings after a successful save', async () => {
+    vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', {}));
+    const queryClient = makeTestQueryClient();
+    const fetchDefaults = vi
+      .fn()
+      .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' })
+      .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' });
+    const defaultsQuery = {
+      queryKey: keys.settings.defaults(),
+      queryFn: fetchDefaults,
+      staleTime: Infinity,
+    };
+    const wrapper = ({ children }: { children: ReactNode }) => (
+      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
+    );
+
+    await queryClient.fetchQuery(defaultsQuery);
+    const { result } = renderHook(() => useAllSettings(), { wrapper });
+
+    await waitFor(() => expect(result.current.fetched).toBe(true));
+    await act(async () => {
+      await result.current.saveAll();
+    });
+
+    const defaults = await queryClient.fetchQuery(defaultsQuery);
+    expect(fetchDefaults).toHaveBeenCalledTimes(2);
+    expect(defaults.subURI).toBe('https://example.com/my_custom_path/');
+  });
+
+  it('keeps cached default settings when a save fails', async () => {
+    vi.spyOn(HttpUtil, 'post').mockImplementation(async (url) => {
+      if (url === '/panel/api/setting/update') return new Msg(false, 'Save failed');
+      return new Msg(true, '', {});
+    });
+    const queryClient = makeTestQueryClient();
+    const fetchDefaults = vi
+      .fn()
+      .mockResolvedValueOnce({ subURI: 'https://example.com/sub/' })
+      .mockResolvedValueOnce({ subURI: 'https://example.com/my_custom_path/' });
+    const defaultsQuery = {
+      queryKey: keys.settings.defaults(),
+      queryFn: fetchDefaults,
+      staleTime: Infinity,
+    };
+    const wrapper = ({ children }: { children: ReactNode }) => (
+      <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
+    );
+
+    await queryClient.fetchQuery(defaultsQuery);
+    const { result } = renderHook(() => useAllSettings(), { wrapper });
+
+    await waitFor(() => expect(result.current.fetched).toBe(true));
+    await act(async () => {
+      await result.current.saveAll();
+    });
+
+    const defaults = await queryClient.fetchQuery(defaultsQuery);
+    expect(fetchDefaults).toHaveBeenCalledOnce();
+    expect(defaults.subURI).toBe('https://example.com/sub/');
+  });
+
   it('accepts legacy overlength regex settings without logging a response validation warning', async () => {
   it('accepts legacy overlength regex settings without logging a response validation warning', async () => {
     const subJsonUserAgentRegex = 'x'.repeat(2_049);
     const subJsonUserAgentRegex = 'x'.repeat(2_049);
     vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', { subJsonUserAgentRegex }));
     vi.spyOn(HttpUtil, 'post').mockResolvedValue(new Msg(true, '', { subJsonUserAgentRegex }));