点赞和改造开关加入后台配置

This commit is contained in:
2026-06-10 14:36:56 +08:00
parent 9db467d23f
commit e29992cf01
33 changed files with 1644 additions and 380 deletions

View File

@@ -81,7 +81,6 @@ describe('apiClient', () => {
body: JSON.stringify({
ok: true,
data: {
ok: true,
token: 'fresh-token',
},
error: null,
@@ -156,7 +155,6 @@ describe('apiClient', () => {
body: JSON.stringify({
ok: true,
data: {
ok: true,
token: 'fresh-token',
},
error: null,
@@ -334,6 +332,64 @@ describe('apiClient', () => {
expect(getStoredAccessToken()).toBe('usable-local-token');
});
it('keeps local token when refresh fails with transient server unavailable', async () => {
setStoredAccessToken('usable-local-token', { emit: false });
fetchMock.mockResolvedValueOnce(createResponseMock({ status: 503 }));
await expect(refreshStoredAccessToken()).rejects.toMatchObject({
status: 503,
});
expect(fetchMock).toHaveBeenCalledWith(
'/api/auth/refresh',
expect.objectContaining({
method: 'POST',
credentials: 'same-origin',
}),
);
expect(dispatchEventMock).not.toHaveBeenCalled();
expect(getStoredAccessToken()).toBe('usable-local-token');
});
it('keeps local token when refresh cannot reach the restarting server', async () => {
setStoredAccessToken('usable-local-token', { emit: false });
fetchMock.mockRejectedValueOnce(new TypeError('Failed to fetch'));
await expect(refreshStoredAccessToken()).rejects.toBeInstanceOf(TypeError);
expect(fetchMock).toHaveBeenCalledTimes(1);
expect(dispatchEventMock).not.toHaveBeenCalled();
expect(getStoredAccessToken()).toBe('usable-local-token');
});
it('clears local token when refresh confirms the session is unauthorized', async () => {
setStoredAccessToken('expired-local-token', { emit: false });
fetchMock.mockResolvedValueOnce(createResponseMock({ status: 401 }));
await expect(refreshStoredAccessToken()).rejects.toMatchObject({
status: 401,
});
expect(dispatchEventMock).not.toHaveBeenCalled();
expect(getStoredAccessToken()).toBe('');
});
it('does not clear auth when protected request refresh fails transiently', async () => {
setStoredAccessToken('expired-token-during-restart', { emit: false });
fetchMock
.mockResolvedValueOnce(createResponseMock({ status: 401 }))
.mockResolvedValueOnce(createResponseMock({ status: 503 }));
const response = await fetchWithApiAuth('/api/runtime/protected', {
method: 'GET',
});
expect(response.status).toBe(401);
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(dispatchEventMock).not.toHaveBeenCalled();
expect(getStoredAccessToken()).toBe('expired-token-during-restart');
});
it('keeps the refreshed token when the retried protected request is still unauthorized', async () => {
setStoredAccessToken('expired-token', { emit: false });
fetchMock
@@ -344,7 +400,6 @@ describe('apiClient', () => {
body: JSON.stringify({
ok: true,
data: {
ok: true,
token: 'fresh-token',
},
error: null,
@@ -366,7 +421,7 @@ describe('apiClient', () => {
expect(dispatchEventMock).not.toHaveBeenCalled();
});
it('rejects refresh responses that do not return a renewed bearer token', async () => {
it('rejects malformed refresh responses without treating them as logout', async () => {
setStoredAccessToken('expired-token', { emit: false });
fetchMock
.mockResolvedValueOnce(createResponseMock({ status: 401 }))
@@ -397,8 +452,8 @@ describe('apiClient', () => {
message: '读取受保护数据失败',
});
expect(fetchMock).toHaveBeenCalledTimes(2);
expect(getStoredAccessToken()).toBe('');
expect(dispatchEventMock).toHaveBeenCalledTimes(1);
expect(getStoredAccessToken()).toBe('expired-token');
expect(dispatchEventMock).not.toHaveBeenCalled();
});
it('keeps the current access token when a public request explicitly skips auth', async () => {