release: v0.16.0
See CHANGELOG.md for the full v0.16.0 entry. --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: ralksta <ralksta@users.noreply.github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Ralf <rohlenbo79@gmail.com> Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com> Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { render, screen, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
||||
import AdminDashboard from '../AdminDashboard';
|
||||
import { SESSION_EXPIRED_EVENT } from '../sessionExpiry';
|
||||
|
||||
vi.mock('next/navigation', () => ({
|
||||
usePathname: () => '/admin',
|
||||
}));
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
/**
|
||||
* `handleReload` used to await POST /api/admin/reload without checking
|
||||
* `res.ok`, so a 401 or a 500 was indistinguishable from success (#596).
|
||||
*/
|
||||
describe('AdminDashboard reload failure reporting', () => {
|
||||
it('reports a non-ok, non-401 reload failure', async () => {
|
||||
const alertMock = vi.fn();
|
||||
vi.stubGlobal('alert', alertMock);
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async (url: string, init?: RequestInit) => {
|
||||
if (url === '/api/admin/reload' && init?.method === 'POST') {
|
||||
return new Response(null, { status: 500 });
|
||||
}
|
||||
return new Response(null, { status: 500 }); // status/doctor probes
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<AdminDashboard onLogout={() => {}}>
|
||||
<div>Panel</div>
|
||||
</AdminDashboard>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Reload config & clear cache'));
|
||||
|
||||
await waitFor(() => expect(alertMock).toHaveBeenCalledWith('Reload failed (HTTP 500).'));
|
||||
});
|
||||
|
||||
it('raises the session-expired event instead of alerting on a 401', async () => {
|
||||
const alertMock = vi.fn();
|
||||
vi.stubGlobal('alert', alertMock);
|
||||
const listener = vi.fn();
|
||||
window.addEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async (url: string, init?: RequestInit) => {
|
||||
if (url === '/api/admin/reload' && init?.method === 'POST') {
|
||||
return new Response(null, { status: 401 });
|
||||
}
|
||||
return new Response(null, { status: 500 });
|
||||
}),
|
||||
);
|
||||
|
||||
render(
|
||||
<AdminDashboard onLogout={() => {}}>
|
||||
<div>Panel</div>
|
||||
</AdminDashboard>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Reload config & clear cache'));
|
||||
|
||||
await waitFor(() => expect(listener).toHaveBeenCalledOnce());
|
||||
expect(alertMock).not.toHaveBeenCalled();
|
||||
window.removeEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
});
|
||||
|
||||
it('does not alert when the reload succeeds', async () => {
|
||||
const alertMock = vi.fn();
|
||||
vi.stubGlobal('alert', alertMock);
|
||||
const fetchMock = vi.fn(async (url: string, init?: RequestInit) => {
|
||||
if (url === '/api/admin/reload' && init?.method === 'POST') {
|
||||
return new Response(null, { status: 200 });
|
||||
}
|
||||
return new Response(null, { status: 500 });
|
||||
});
|
||||
vi.stubGlobal('fetch', fetchMock);
|
||||
|
||||
render(
|
||||
<AdminDashboard onLogout={() => {}}>
|
||||
<div>Panel</div>
|
||||
</AdminDashboard>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByTitle('Reload config & clear cache'));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(
|
||||
fetchMock.mock.calls.filter(
|
||||
([url, init]) => url === '/api/admin/reload' && init?.method === 'POST',
|
||||
),
|
||||
).toHaveLength(1),
|
||||
);
|
||||
expect(alertMock).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
38
app/admin/components/__tests__/sessionExpiry.test.ts
Normal file
38
app/admin/components/__tests__/sessionExpiry.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
// @vitest-environment jsdom
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { SESSION_EXPIRED_EVENT, reportIfSessionExpired } from '../sessionExpiry';
|
||||
|
||||
/**
|
||||
* Sessions last 24 hours, but nothing re-checked authentication after the
|
||||
* mount-time check in AdminShell — every admin action after that just failed
|
||||
* quietly with the UI still looking signed in (#596). This is the one place
|
||||
* every admin fetch call site is meant to route a non-ok response through.
|
||||
*/
|
||||
describe('reportIfSessionExpired', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('dispatches the event and returns true for a 401', () => {
|
||||
const listener = vi.fn();
|
||||
window.addEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
|
||||
const result = reportIfSessionExpired(new Response(null, { status: 401 }));
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(listener).toHaveBeenCalledOnce();
|
||||
window.removeEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
});
|
||||
|
||||
it('does nothing and returns false for other statuses', () => {
|
||||
const listener = vi.fn();
|
||||
window.addEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
|
||||
for (const status of [200, 400, 403, 404, 500]) {
|
||||
expect(reportIfSessionExpired(new Response(null, { status }))).toBe(false);
|
||||
}
|
||||
|
||||
expect(listener).not.toHaveBeenCalled();
|
||||
window.removeEventListener(SESSION_EXPIRED_EVENT, listener);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user