| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- import { TestBed } from '@angular/core/testing';
- import { SessionService } from './session.service';
- import { Me } from './api/models';
- /**
- * 会话状态(登录态)—— 全项目唯一持有者。
- *
- * 这里出错影响最大:登录态判断错了,整个 App 都会显示异常。
- * 重点回归:
- * ① 持久化与恢复(刷新后仍登录)
- * ② logout 必须清干净(含旧版兼容键)
- * ③ localStorage 不可用时降级为内存态,不崩
- */
- describe('SessionService', () => {
- const ME: Me = { id: 'u1', name: '用户9836', mobile: '19170119836' };
- beforeEach(() => localStorage.clear());
- afterEach(() => localStorage.clear());
- function make(): SessionService {
- TestBed.resetTestingModule();
- TestBed.configureTestingModule({ providers: [SessionService] });
- return TestBed.inject(SessionService);
- }
- it('初始态未登录', () => {
- const s = make();
- expect(s.isLoggedIn()).toBeFalse();
- expect(s.token()).toBeNull();
- });
- it('setSession 后 isLoggedIn 为真,且写入 localStorage', () => {
- const s = make();
- s.setSession('r:abc', ME);
- expect(s.isLoggedIn()).toBeTrue();
- expect(localStorage.getItem('fmode_session_token')).toBe('r:abc');
- expect(JSON.parse(localStorage.getItem('fmode_me')!)).toEqual(ME);
- });
- it('🔴 刷新后能恢复登录态(新实例读同一份 localStorage)', () => {
- make().setSession('r:abc', ME);
- const s2 = make(); // 模拟刷新后重新创建
- expect(s2.isLoggedIn()).toBeTrue();
- expect(s2.token()).toBe('r:abc');
- expect(s2.me()?.name).toBe('用户9836');
- });
- it('🔴 logout 必须清干净(token / me / localStorage)', () => {
- const s = make();
- s.setSession('r:abc', ME);
- s.logout();
- expect(s.token()).toBeNull();
- expect(s.me()).toBeNull();
- expect(s.isLoggedIn()).toBeFalse();
- expect(localStorage.getItem('fmode_session_token')).toBeNull();
- expect(localStorage.getItem('fmode_me')).toBeNull();
- });
- it('只有 token 没有 me → 仍视为未登录', () => {
- localStorage.setItem('fmode_session_token', 'r:abc');
- const s = make();
- expect(s.token()).toBe('r:abc');
- expect(s.isLoggedIn()).toBeFalse(); // 缺 me 不算登录
- });
- it('me 的 JSON 损坏 → 不崩,视为未登录', () => {
- localStorage.setItem('fmode_session_token', 'r:abc');
- localStorage.setItem('fmode_me', '{坏掉的json');
- const s = make();
- expect(s.me()).toBeNull();
- expect(s.isLoggedIn()).toBeFalse();
- });
- it('setMe 单独更新身份(不换 token)', () => {
- const s = make();
- s.setSession('r:abc', ME);
- s.setMe({ ...ME, name: '改名了' });
- expect(s.token()).toBe('r:abc');
- expect(s.me()?.name).toBe('改名了');
- });
- /*
- * session 过期(2026-11-12)。
- *
- * 密码登录的账号带 `expiresAt`(实测有效期 1 年)。
- * 过期后必须能本地判出来,否则用户会看到一堆 401 却不知道原因。
- */
- describe('过期时间', () => {
- it('未过期 → 仍算已登录', () => {
- const s = make();
- const future = new Date(Date.now() + 86_400_000).toISOString();
- s.setSession('r:abc', ME, future);
- expect(s.isExpired()).toBeFalse();
- expect(s.isLoggedIn()).toBeTrue();
- });
- it('已过期 → isExpired=true 且 isLoggedIn=false', () => {
- const s = make();
- const past = new Date(Date.now() - 1000).toISOString();
- s.setSession('r:abc', ME, past);
- expect(s.isExpired()).toBeTrue();
- expect(s.isLoggedIn()).toBeFalse();
- });
- it('🔴 不传 expiresAt → 视为不过期(老登录态不能被踢下线)', () => {
- const s = make();
- s.setSession('r:abc', ME);
- expect(s.expiresAt()).toBeNull();
- expect(s.isExpired()).toBeFalse();
- expect(s.isLoggedIn()).toBeTrue();
- });
- it('过期时间跨实例持久化(刷新后仍生效)', () => {
- const past = new Date(Date.now() - 1000).toISOString();
- make().setSession('r:abc', ME, past);
- expect(make().isExpired()).toBeTrue();
- });
- it('logout 一并清除过期时间', () => {
- const s = make();
- s.setSession('r:abc', ME, new Date(Date.now() + 86_400_000).toISOString());
- s.logout();
- expect(s.expiresAt()).toBeNull();
- expect(make().isExpired()).toBeFalse();
- });
- it('格式非法的过期时间 → 不误判为过期', () => {
- const s = make();
- s.setSession('r:abc', ME, 'not-a-date');
- expect(s.isExpired()).toBeFalse();
- });
- });
- });
|