Files
ctms/frontend/src/views/Login.test.ts
T
Cheng Zhou b491b6a146
Client Quality Gates / Shared client and Web (push) Has been cancelled
Client Quality Gates / macOS Desktop (push) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (push) Has been cancelled
Client Quality Gates / Shared client and Web (pull_request) Has been cancelled
Client Quality Gates / macOS Desktop (pull_request) Has been cancelled
Storage Persistence Guard / storage-persistence-audit (pull_request) Has been cancelled
发布候选:整合桌面端界面与发布稳定化里程碑
2026-07-01 10:53:24 +08:00

111 lines
5.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
const readLoginView = () => readFileSync(resolve(__dirname, "./Login.vue"), "utf8");
describe("Login protocol agreement", () => {
it("requires protocol agreement before calling login", () => {
const source = readLoginView();
const protocolGuardIndex = source.indexOf("!form.agreeProtocol");
const loginCallIndex = source.indexOf("auth.login(form.email, form.password)");
expect(source).toContain('v-model="form.agreeProtocol"');
expect(source).toContain("我已阅读并同意");
expect(source).toContain("请先阅读并同意用户协议");
expect(protocolGuardIndex).toBeGreaterThan(-1);
expect(loginCallIndex).toBeGreaterThan(-1);
expect(protocolGuardIndex).toBeLessThan(loginCallIndex);
});
it("does not access browser password credentials on the login page", () => {
const source = readLoginView();
expect(source).toContain('autocomplete="username"');
expect(source).toContain('name="username"');
expect(source).toContain('name="ctms-login-password"');
expect(source).toContain('autocomplete="new-password"');
expect(source).not.toContain('v-model="form.rememberPassword"');
expect(source).not.toContain("记住密码");
expect(source).not.toContain("tryLoadBrowserCredential");
expect(source).not.toContain("tryStoreBrowserCredential");
expect(source).not.toContain("navigator.credentials");
expect(source).not.toContain("PasswordCredential");
expect(source).not.toContain('localStorage.setItem("ctms_saved_password"');
expect(source).not.toContain("localStorage.setItem('ctms_saved_password'");
});
it("opens a CTMS-specific protocol dialog from the protocol text", () => {
const source = readLoginView();
expect(source).toContain("authProtocolSections");
expect(source).toContain('v-model="protocolDialogVisible"');
expect(source).toContain('@click.stop.prevent="openProtocolDialog"');
expect(source).toContain('import { authProtocolSections } from "../content/authProtocol"');
expect(source).toContain("const protocolSections = authProtocolSections");
expect(source).toContain("confirmProtocol");
});
it("persists the protocol agreement checkbox in localStorage", () => {
const source = readLoginView();
expect(source).toContain("const AGREE_PROTOCOL_KEY = \"ctms_agree_protocol\"");
expect(source).toContain('localStorage.getItem(AGREE_PROTOCOL_KEY) === "true"');
expect(source).toContain('localStorage.setItem(AGREE_PROTOCOL_KEY, String(checked))');
expect(source).toContain("confirmProtocol");
});
it("avoids browser password caching", () => {
const source = readLoginView();
expect(source).toContain('name="ctms-login-password"');
expect(source).toContain('autocomplete="new-password"');
expect(source).not.toContain('autocomplete="current-password"');
});
it("restores the current user's last project before routing after login", () => {
const source = readLoginView();
const loginCallIndex = source.indexOf("auth.login(form.email, form.password)");
const userKeyIndex = source.indexOf("const userKey = auth.user?.email || form.email");
const restoreIndex = source.indexOf("studyStore.restoreStudyForUser(userKey");
const projectOverviewRouteIndex = source.indexOf('router.push("/project/overview")');
expect(userKeyIndex).toBeGreaterThan(loginCallIndex);
expect(restoreIndex).toBeGreaterThan(loginCallIndex);
expect(restoreIndex).toBeLessThan(projectOverviewRouteIndex);
expect(source).toContain("preferActive: !!auth.user?.is_admin");
});
it("shows persistent logout reason notices on the login card", () => {
const source = readLoginView();
expect(source).toContain('v-if="logoutNotice"');
expect(source).toContain("LOGOUT_REASON_TIMEOUT");
expect(source).toContain("LOGOUT_REASON_AUTH_EXPIRED");
expect(source).toContain("LOGOUT_REASON_MANUAL");
expect(source).toContain("30 分钟未检测到任何操作。为保护项目数据,请重新登录后继续。");
expect(source).toContain("登录状态已失效");
expect(source).toContain("已安全退出");
expect(source).toContain(".logout-notice");
});
it("keeps the login card wide enough on desktop while remaining responsive", () => {
const source = readLoginView();
expect(source).toContain("width: clamp(480px, 34vw, 560px);");
expect(source).toContain("max-width: calc(100vw - 40px);");
});
it("uses only server-configured email domains on web and desktop", () => {
const source = readLoginView();
expect(source).toContain('fetchEmailDomains()');
expect(source).toContain(':disabled="availableEmailDomains.length === 0"');
expect(source).toContain("const availableEmailDomains = computed(() => configuredEmailDomains.value)");
expect(source).toContain("configuredEmailDomains.value.includes(domain)");
expect(source).not.toContain("preservedEmailDomain");
expect(source).not.toContain("showDomainSelect");
expect(source).not.toContain("email-domain-input");
});
});