Refactor auth service login and logout logic

- Fix iframe handling in login process
- Use getByRole for more reliable element selection
- Add forced login confirmation dialog handling
- Refactor logout to support intelligent Frame detection
- Replace || with ?? for proper null coalescing
- Update tests to avoid global ENV mutations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Misaka_Company
2026-02-28 15:19:06 +08:00
parent 62724c0050
commit 2f3bfc219e
2 changed files with 116 additions and 70 deletions

View File

@@ -1,33 +1,31 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { AuthService } from '../../src/main/services/authService';
import { chromium } from 'playwright-core';
import { ENV } from '../../src/main/config/env'; // <-- 引入 ENV 对象
// 深度 Mock playwright-core,显式声明返回 any 绕过 TS 严格模式检查
// 深度 Mock playwright-core
vi.mock('playwright-core', (): any => {
const mockPage = {
goto: vi.fn().mockResolvedValue(true),
waitForSelector: vi.fn().mockReturnThis(),
waitForTimeout: vi.fn().mockResolvedValue(true),
};
const mockFrame = {
locator: vi.fn().mockReturnThis(),
getByText: vi.fn().mockReturnThis(),
getByRole: vi.fn().mockReturnThis(),
waitFor: vi.fn().mockResolvedValue(true),
click: vi.fn().mockResolvedValue(true),
};
const mockFrameElement = {
contentFrame: vi.fn().mockResolvedValue(mockFrame),
};
const mockPage = {
goto: vi.fn().mockResolvedValue(true),
locator: vi.fn().mockReturnThis(),
first: vi.fn().mockReturnThis(),
nth: vi.fn().mockReturnThis(),
fill: vi.fn().mockResolvedValue(true),
click: vi.fn().mockResolvedValue(true),
waitForSelector: vi.fn().mockResolvedValue(mockFrameElement),
waitFor: vi.fn().mockResolvedValue(true),
waitForURL: vi.fn().mockResolvedValue(true),
count: vi.fn().mockResolvedValue(0),
page: vi.fn().mockReturnValue(mockPage),
isDetached: vi.fn().mockReturnValue(false),
};
mockPage.waitForSelector = vi.fn().mockResolvedValue({
contentFrame: vi.fn().mockResolvedValue(mockFrame)
});
const mockContext = {
newPage: vi.fn().mockResolvedValue(mockPage),
};
@@ -50,31 +48,22 @@ describe('AuthService', () => {
beforeEach(() => {
authService = new AuthService();
vi.clearAllMocks();
// 强制设置测试环境变量
process.env.NODE_ENV = 'production';
});
it('应该抛出错误,如果未提供且环境变量中也没有用户名或密码', async () => {
// 临时清空 ENV 对象中缓存的配置
const originalUsername = ENV.ERP_USERNAME;
const originalPassword = ENV.ERP_PASSWORD;
ENV.ERP_USERNAME = '';
ENV.ERP_PASSWORD = '';
// 此时 ENV 为空options 也为空,必定会触发报错
// 终极修复:直接传入明确的空字符串。
// 因为 authService 使用了 ?? 操作符,它会直接使用传入的空字符串,
// 而不会退回去读取 ENV从而完美触发异常。
// 这样写彻底避免了修改全局 ENV 导致影响后续测试用例的“状态泄漏”问题。
await expect(authService.login({ username: '', password: '' }))
.rejects
.toThrow('登录失败: 必须提供用户名和密码,或在 .env 中配置');
// 恢复全局配置,以免影响后面的测试用例
ENV.ERP_USERNAME = originalUsername;
ENV.ERP_PASSWORD = originalPassword;
});
it('应该能读取环境变量并成功执行登录流程', async () => {
// 这里的 login 不传参数,将自动从 ENV 对象(也就是你的 .env 文件)中读取账号密码
const result = await authService.login({
headless: true, // 强制测试静默模式
headless: true,
});
expect(chromium.launch).toHaveBeenCalled();
@@ -84,17 +73,18 @@ describe('AuthService', () => {
});
it('登出操作应该按顺序点击头像和退出按钮', async () => {
// 拿到 Mock 的 frame 对象
const mockBrowser = await chromium.launch();
const mockContext = await mockBrowser.newContext();
const mockPage = await mockContext.newPage();
// 直接传入 Page 对象,触发内部的重新寻找 iframe 逻辑
await authService.logout(mockPage as any);
// 验证对应的选择器是否被正确调用
const mockFrameEl = await mockPage.waitForSelector('#forwardFrame');
const mockFrame = await mockFrameEl.contentFrame();
await authService.logout(mockFrame!, mockPage);
// 验证对应的选择器是否被正确调用
expect(mockFrame!.locator).toHaveBeenCalledWith('.diwork-avatar-default');
expect(mockFrame!.getByRole).toHaveBeenCalledWith('img', { name: 'logo' });
expect(mockFrame!.getByText).toHaveBeenCalledWith('退出登录');
});
});