diff --git a/src/server/services/checkinService.autoRelogin.test.ts b/src/server/services/checkinService.autoRelogin.test.ts index 1d5d02c..913c6e3 100644 --- a/src/server/services/checkinService.autoRelogin.test.ts +++ b/src/server/services/checkinService.autoRelogin.test.ts @@ -340,6 +340,40 @@ describe('checkinService auto relogin', () => { expect(notifyMock).not.toHaveBeenCalled(); }); + it('skips account updates when unsupported checkin responses do not change account state', async () => { + selectAllMock.mockReturnValue([ + { + accounts: { + id: 18, + username: 'plain-user', + accessToken: 'token', + status: 'active', + extraConfig: null, + }, + sites: { + id: 18, + name: 'done-hub', + url: 'https://done.example.com', + platform: 'donehub', + }, + }, + ]); + + adapterMock.checkin.mockResolvedValue({ + success: false, + message: 'checkin endpoint not found', + }); + + const { checkinAccount } = await import('./checkinService.js'); + const result = await checkinAccount(18); + + expect(result.success).toBe(true); + expect(result.status).toBe('skipped'); + expect(updateSetMock).not.toHaveBeenCalled(); + const firstInsertPayload = insertValuesMock.mock.calls[0]?.[0] as Record; + expect(firstInsertPayload?.status).toBe('skipped'); + }); + it('treats sub2api checkin unsupported message as skipped', async () => { selectAllMock.mockReturnValue([ { diff --git a/src/server/services/checkinService.ts b/src/server/services/checkinService.ts index d2f2190..6688b41 100644 --- a/src/server/services/checkinService.ts +++ b/src/server/services/checkinService.ts @@ -226,10 +226,12 @@ export async function checkinAccount(accountId: number, options?: { skipEvent?: updates.updatedAt = new Date().toISOString(); } - await db.update(schema.accounts) - .set(updates) - .where(eq(schema.accounts.id, accountId)) - .run(); + if (Object.keys(updates).length > 0) { + await db.update(schema.accounts) + .set(updates) + .where(eq(schema.accounts.id, accountId)) + .run(); + } if (shouldRefreshBalance) { try { diff --git a/src/web/pages/Settings.tsx b/src/web/pages/Settings.tsx index d795734..51d263b 100644 --- a/src/web/pages/Settings.tsx +++ b/src/web/pages/Settings.tsx @@ -22,6 +22,17 @@ const PROXY_TOKEN_PREFIX = 'sk-'; const ROUTE_BRAND_ICON_PREFIX = 'brand:'; const FACTORY_RESET_ADMIN_TOKEN = 'change-me-admin-token'; const FACTORY_RESET_CONFIRM_SECONDS = 3; +const CHECKIN_SCHEDULE_MODE_OPTIONS = [ + { value: 'cron', label: 'Cron' }, + { value: 'interval', label: '间隔签到' }, +] as const; +const CHECKIN_INTERVAL_OPTIONS = Array.from({ length: 24 }, (_, index) => { + const hour = index + 1; + return { + value: String(hour), + label: `${hour} 小时`, + }; +}); type DbDialect = 'sqlite' | 'mysql' | 'postgres'; type RuntimeSettings = { @@ -979,36 +990,33 @@ export default function Settings() {
签到方式
- + options={CHECKIN_SCHEDULE_MODE_OPTIONS.map((item) => ({ ...item }))} + />
签到间隔
- + options={CHECKIN_INTERVAL_OPTIONS} + />
-
diff --git a/src/web/pages/settings.schedule-log-cleanup.test.tsx b/src/web/pages/settings.schedule-log-cleanup.test.tsx index 7f89432..d9e8aee 100644 --- a/src/web/pages/settings.schedule-log-cleanup.test.tsx +++ b/src/web/pages/settings.schedule-log-cleanup.test.tsx @@ -2,6 +2,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { act, create, type ReactTestInstance } from 'react-test-renderer'; import { MemoryRouter } from 'react-router-dom'; import { ToastProvider } from '../components/Toast.js'; +import ModernSelect from '../components/ModernSelect.js'; import Settings from './Settings.js'; const { apiMock } = vi.hoisted(() => ({ @@ -145,4 +146,32 @@ describe('Settings log cleanup schedule', () => { root?.unmount(); } }); + + it('renders schedule mode controls with modern selects and ghost action styling', async () => { + let root: ReturnType | null = null; + try { + await act(async () => { + root = create( + + + + + , + ); + }); + await flushMicrotasks(); + + const triggerButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && collectText(node).trim() === '测试一次签到' + )); + + expect(root.root.findAllByType('select')).toHaveLength(0); + expect(root.root.findAllByType(ModernSelect).length).toBeGreaterThanOrEqual(3); + expect(String(triggerButton.props.className || '')).toContain('btn-ghost'); + } finally { + root?.unmount(); + } + }); });