test(zulip-accounts):完善zulip_accounts模块测试覆盖
范围:src/core/db/zulip_accounts/ - 添加ZulipAccounts实体业务方法测试 - 添加ZulipAccountsModule动态配置测试 - 提升测试覆盖率,确保实体逻辑和模块配置的正确性 - 测试包含状态管理、时间判断、错误处理等核心业务逻辑
This commit is contained in:
468
src/core/db/zulip_accounts/zulip_accounts.entity.spec.ts
Normal file
468
src/core/db/zulip_accounts/zulip_accounts.entity.spec.ts
Normal file
@@ -0,0 +1,468 @@
|
||||
/**
|
||||
* Zulip账号关联实体测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试实体的业务方法逻辑
|
||||
* - 验证状态管理和判断方法
|
||||
* - 测试时间相关的业务逻辑
|
||||
* - 验证错误处理和重试机制
|
||||
*
|
||||
* 测试范围:
|
||||
* - 状态判断方法(isActive, isHealthy, canBeDeleted等)
|
||||
* - 时间相关方法(isStale, needsVerification等)
|
||||
* - 状态更新方法(activate, suspend, deactivate等)
|
||||
* - 错误处理方法(setError, clearError等)
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 创建测试文件,确保实体业务方法的测试覆盖 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.0
|
||||
* @since 2026-01-12
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { ZulipAccounts } from './zulip_accounts.entity';
|
||||
import {
|
||||
DEFAULT_MAX_RETRY_COUNT,
|
||||
HIGH_RETRY_THRESHOLD,
|
||||
DEFAULT_MAX_AGE_DAYS,
|
||||
DEFAULT_VERIFICATION_HOURS,
|
||||
MILLISECONDS_PER_DAY,
|
||||
MILLISECONDS_PER_HOUR
|
||||
} from './zulip_accounts.constants';
|
||||
|
||||
describe('ZulipAccounts Entity', () => {
|
||||
let entity: ZulipAccounts;
|
||||
|
||||
beforeEach(() => {
|
||||
entity = new ZulipAccounts();
|
||||
entity.id = BigInt(1);
|
||||
entity.gameUserId = BigInt(123);
|
||||
entity.zulipUserId = 12345;
|
||||
entity.zulipEmail = 'test@example.com';
|
||||
entity.zulipFullName = 'Test User';
|
||||
entity.zulipApiKeyEncrypted = 'encrypted-key';
|
||||
entity.status = 'active';
|
||||
entity.retryCount = 0;
|
||||
entity.errorMessage = null;
|
||||
entity.createdAt = new Date();
|
||||
entity.updatedAt = new Date();
|
||||
entity.lastVerifiedAt = new Date();
|
||||
entity.lastSyncedAt = new Date();
|
||||
});
|
||||
|
||||
describe('isActive()', () => {
|
||||
it('should return true when status is active', () => {
|
||||
entity.status = 'active';
|
||||
expect(entity.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when status is not active', () => {
|
||||
entity.status = 'inactive';
|
||||
expect(entity.isActive()).toBe(false);
|
||||
|
||||
entity.status = 'suspended';
|
||||
expect(entity.isActive()).toBe(false);
|
||||
|
||||
entity.status = 'error';
|
||||
expect(entity.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isHealthy()', () => {
|
||||
it('should return true when status is active and retry count is low', () => {
|
||||
entity.status = 'active';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.isHealthy()).toBe(true);
|
||||
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT - 1;
|
||||
expect(entity.isHealthy()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when status is not active', () => {
|
||||
entity.status = 'inactive';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
|
||||
entity.status = 'error';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when retry count exceeds limit', () => {
|
||||
entity.status = 'active';
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT;
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT + 1;
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('canBeDeleted()', () => {
|
||||
it('should return true when status is not active', () => {
|
||||
entity.status = 'inactive';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.canBeDeleted()).toBe(true);
|
||||
|
||||
entity.status = 'suspended';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.canBeDeleted()).toBe(true);
|
||||
|
||||
entity.status = 'error';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.canBeDeleted()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true when retry count exceeds high threshold', () => {
|
||||
entity.status = 'active';
|
||||
entity.retryCount = HIGH_RETRY_THRESHOLD + 1;
|
||||
expect(entity.canBeDeleted()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when status is active and retry count is low', () => {
|
||||
entity.status = 'active';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.canBeDeleted()).toBe(false);
|
||||
|
||||
entity.retryCount = HIGH_RETRY_THRESHOLD;
|
||||
expect(entity.canBeDeleted()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isStale()', () => {
|
||||
it('should return false for recently updated entity', () => {
|
||||
entity.updatedAt = new Date(); // 刚刚更新
|
||||
expect(entity.isStale()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for old entity using default max age', () => {
|
||||
const oldDate = new Date();
|
||||
oldDate.setTime(oldDate.getTime() - (DEFAULT_MAX_AGE_DAYS * MILLISECONDS_PER_DAY + 1000));
|
||||
entity.updatedAt = oldDate;
|
||||
expect(entity.isStale()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for entity within default max age', () => {
|
||||
const recentDate = new Date();
|
||||
recentDate.setTime(recentDate.getTime() - (DEFAULT_MAX_AGE_DAYS * MILLISECONDS_PER_DAY - 1000));
|
||||
entity.updatedAt = recentDate;
|
||||
expect(entity.isStale()).toBe(false);
|
||||
});
|
||||
|
||||
it('should respect custom max age parameter', () => {
|
||||
const customMaxAge = 2 * MILLISECONDS_PER_DAY; // 2天
|
||||
const oldDate = new Date();
|
||||
oldDate.setTime(oldDate.getTime() - (customMaxAge + 1000));
|
||||
entity.updatedAt = oldDate;
|
||||
|
||||
expect(entity.isStale(customMaxAge)).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle edge case at exact max age boundary', () => {
|
||||
const exactDate = new Date();
|
||||
exactDate.setTime(exactDate.getTime() - (DEFAULT_MAX_AGE_DAYS * MILLISECONDS_PER_DAY));
|
||||
entity.updatedAt = exactDate;
|
||||
expect(entity.isStale()).toBe(false); // 等于边界值应该返回false
|
||||
});
|
||||
});
|
||||
|
||||
describe('needsVerification()', () => {
|
||||
it('should return true when lastVerifiedAt is null', () => {
|
||||
entity.lastVerifiedAt = null;
|
||||
expect(entity.needsVerification()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for recently verified entity', () => {
|
||||
entity.lastVerifiedAt = new Date(); // 刚刚验证
|
||||
expect(entity.needsVerification()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for old verification using default max age', () => {
|
||||
const oldDate = new Date();
|
||||
oldDate.setTime(oldDate.getTime() - (DEFAULT_VERIFICATION_HOURS * MILLISECONDS_PER_HOUR + 1000));
|
||||
entity.lastVerifiedAt = oldDate;
|
||||
expect(entity.needsVerification()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false for verification within default max age', () => {
|
||||
const recentDate = new Date();
|
||||
recentDate.setTime(recentDate.getTime() - (DEFAULT_VERIFICATION_HOURS * MILLISECONDS_PER_HOUR - 1000));
|
||||
entity.lastVerifiedAt = recentDate;
|
||||
expect(entity.needsVerification()).toBe(false);
|
||||
});
|
||||
|
||||
it('should respect custom max age parameter', () => {
|
||||
const customMaxAge = 2 * MILLISECONDS_PER_HOUR; // 2小时
|
||||
const oldDate = new Date();
|
||||
oldDate.setTime(oldDate.getTime() - (customMaxAge + 1000));
|
||||
entity.lastVerifiedAt = oldDate;
|
||||
|
||||
expect(entity.needsVerification(customMaxAge)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldRetry()', () => {
|
||||
it('should return true when status is error and retry count is below limit', () => {
|
||||
entity.status = 'error';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.shouldRetry()).toBe(true);
|
||||
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT - 1;
|
||||
expect(entity.shouldRetry()).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when status is not error', () => {
|
||||
entity.status = 'active';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.shouldRetry()).toBe(false);
|
||||
|
||||
entity.status = 'inactive';
|
||||
entity.retryCount = 0;
|
||||
expect(entity.shouldRetry()).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when retry count exceeds limit', () => {
|
||||
entity.status = 'error';
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT;
|
||||
expect(entity.shouldRetry()).toBe(false);
|
||||
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT + 1;
|
||||
expect(entity.shouldRetry()).toBe(false);
|
||||
});
|
||||
|
||||
it('should respect custom max retry count parameter', () => {
|
||||
const customMaxRetry = 2;
|
||||
entity.status = 'error';
|
||||
entity.retryCount = 1;
|
||||
expect(entity.shouldRetry(customMaxRetry)).toBe(true);
|
||||
|
||||
entity.retryCount = 2;
|
||||
expect(entity.shouldRetry(customMaxRetry)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateVerificationTime()', () => {
|
||||
it('should update lastVerifiedAt and updatedAt to current time', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.lastVerifiedAt = new Date('2020-01-01');
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.updateVerificationTime();
|
||||
|
||||
expect(entity.lastVerifiedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateSyncTime()', () => {
|
||||
it('should update lastSyncedAt and updatedAt to current time', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.lastSyncedAt = new Date('2020-01-01');
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.updateSyncTime();
|
||||
|
||||
expect(entity.lastSyncedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('setError()', () => {
|
||||
it('should set status to error and update error message and retry count', () => {
|
||||
const errorMessage = 'Test error message';
|
||||
entity.status = 'active';
|
||||
entity.errorMessage = null;
|
||||
entity.retryCount = 0;
|
||||
|
||||
entity.setError(errorMessage);
|
||||
|
||||
expect(entity.status).toBe('error');
|
||||
expect(entity.errorMessage).toBe(errorMessage);
|
||||
expect(entity.retryCount).toBe(1);
|
||||
});
|
||||
|
||||
it('should increment retry count on subsequent errors', () => {
|
||||
entity.status = 'error';
|
||||
entity.retryCount = 2;
|
||||
|
||||
entity.setError('Another error');
|
||||
|
||||
expect(entity.retryCount).toBe(3);
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.setError('Test error');
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('clearError()', () => {
|
||||
it('should clear error status and message when status is error', () => {
|
||||
entity.status = 'error';
|
||||
entity.errorMessage = 'Some error';
|
||||
|
||||
entity.clearError();
|
||||
|
||||
expect(entity.status).toBe('active');
|
||||
expect(entity.errorMessage).toBeNull();
|
||||
});
|
||||
|
||||
it('should not change status when not in error state', () => {
|
||||
entity.status = 'inactive';
|
||||
entity.errorMessage = null;
|
||||
|
||||
entity.clearError();
|
||||
|
||||
expect(entity.status).toBe('inactive');
|
||||
expect(entity.errorMessage).toBeNull();
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp when clearing error', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.status = 'error';
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.clearError();
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('resetRetryCount()', () => {
|
||||
it('should reset retry count to zero', () => {
|
||||
entity.retryCount = 5;
|
||||
|
||||
entity.resetRetryCount();
|
||||
|
||||
expect(entity.retryCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.resetRetryCount();
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('activate()', () => {
|
||||
it('should set status to active and clear error message and retry count', () => {
|
||||
entity.status = 'error';
|
||||
entity.errorMessage = 'Some error';
|
||||
entity.retryCount = 3;
|
||||
|
||||
entity.activate();
|
||||
|
||||
expect(entity.status).toBe('active');
|
||||
expect(entity.errorMessage).toBeNull();
|
||||
expect(entity.retryCount).toBe(0);
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.activate();
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('suspend()', () => {
|
||||
it('should set status to suspended', () => {
|
||||
entity.status = 'active';
|
||||
|
||||
entity.suspend();
|
||||
|
||||
expect(entity.status).toBe('suspended');
|
||||
});
|
||||
|
||||
it('should set error message when reason is provided', () => {
|
||||
const reason = 'Suspended for maintenance';
|
||||
entity.errorMessage = null;
|
||||
|
||||
entity.suspend(reason);
|
||||
|
||||
expect(entity.errorMessage).toBe(reason);
|
||||
});
|
||||
|
||||
it('should not change error message when no reason is provided', () => {
|
||||
const existingMessage = 'Existing message';
|
||||
entity.errorMessage = existingMessage;
|
||||
|
||||
entity.suspend();
|
||||
|
||||
expect(entity.errorMessage).toBe(existingMessage);
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.suspend();
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('deactivate()', () => {
|
||||
it('should set status to inactive', () => {
|
||||
entity.status = 'active';
|
||||
|
||||
entity.deactivate();
|
||||
|
||||
expect(entity.status).toBe('inactive');
|
||||
});
|
||||
|
||||
it('should update updatedAt timestamp', () => {
|
||||
const beforeUpdate = new Date();
|
||||
entity.updatedAt = new Date('2020-01-01');
|
||||
|
||||
entity.deactivate();
|
||||
|
||||
expect(entity.updatedAt.getTime()).toBeGreaterThanOrEqual(beforeUpdate.getTime());
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases and boundary conditions', () => {
|
||||
it('should handle null dates gracefully in time-based methods', () => {
|
||||
entity.lastVerifiedAt = null;
|
||||
entity.updatedAt = null as any; // 强制设置为null进行边界测试
|
||||
|
||||
expect(() => entity.needsVerification()).not.toThrow();
|
||||
expect(entity.needsVerification()).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle zero retry count correctly', () => {
|
||||
entity.retryCount = 0;
|
||||
entity.status = 'error';
|
||||
|
||||
expect(entity.shouldRetry()).toBe(true);
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle maximum retry count boundary', () => {
|
||||
entity.retryCount = DEFAULT_MAX_RETRY_COUNT;
|
||||
entity.status = 'error';
|
||||
|
||||
expect(entity.shouldRetry()).toBe(false);
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
});
|
||||
|
||||
it('should handle very large retry counts', () => {
|
||||
entity.retryCount = 999999;
|
||||
entity.status = 'active';
|
||||
|
||||
expect(entity.isHealthy()).toBe(false);
|
||||
expect(entity.canBeDeleted()).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
327
src/core/db/zulip_accounts/zulip_accounts.module.spec.ts
Normal file
327
src/core/db/zulip_accounts/zulip_accounts.module.spec.ts
Normal file
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Zulip账号关联数据模块测试
|
||||
*
|
||||
* 功能描述:
|
||||
* - 测试动态模块配置的正确性
|
||||
* - 验证数据库和内存模式的切换逻辑
|
||||
* - 测试依赖注入配置的完整性
|
||||
* - 验证环境自适应功能
|
||||
*
|
||||
* 测试范围:
|
||||
* - forDatabase()方法的模块配置
|
||||
* - forMemory()方法的模块配置
|
||||
* - forRoot()方法的自动选择逻辑
|
||||
* - isDatabaseConfigured()函数的环境检测
|
||||
*
|
||||
* 最近修改:
|
||||
* - 2026-01-12: 代码规范优化 - 创建测试文件,确保模块配置逻辑的测试覆盖 (修改者: moyin)
|
||||
* - 2026-01-12: 代码规范优化 - 修复测试依赖注入问题,简化集成测试 (修改者: moyin)
|
||||
*
|
||||
* @author moyin
|
||||
* @version 1.0.1
|
||||
* @since 2026-01-12
|
||||
* @lastModified 2026-01-12
|
||||
*/
|
||||
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { CacheModule } from '@nestjs/cache-manager';
|
||||
import { ZulipAccountsModule } from './zulip_accounts.module';
|
||||
import { ZulipAccounts } from './zulip_accounts.entity';
|
||||
import { ZulipAccountsRepository } from './zulip_accounts.repository';
|
||||
import { ZulipAccountsMemoryRepository } from './zulip_accounts_memory.repository';
|
||||
import { ZulipAccountsService } from './zulip_accounts.service';
|
||||
import { ZulipAccountsMemoryService } from './zulip_accounts_memory.service';
|
||||
import { AppLoggerService } from '../../utils/logger/logger.service';
|
||||
import { REQUIRED_DB_ENV_VARS } from './zulip_accounts.constants';
|
||||
|
||||
describe('ZulipAccountsModule', () => {
|
||||
let originalEnv: NodeJS.ProcessEnv;
|
||||
|
||||
beforeEach(() => {
|
||||
// 保存原始环境变量
|
||||
originalEnv = { ...process.env };
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// 恢复原始环境变量
|
||||
process.env = originalEnv;
|
||||
});
|
||||
|
||||
describe('forDatabase()', () => {
|
||||
it('should create database module with correct configuration', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
expect(module).toBeDefined();
|
||||
expect(module.module).toBe(ZulipAccountsModule);
|
||||
expect(module.imports).toHaveLength(2);
|
||||
expect(module.providers).toHaveLength(3);
|
||||
expect(module.exports).toHaveLength(4);
|
||||
});
|
||||
|
||||
it('should include TypeORM configuration for ZulipAccounts entity', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
// 检查是否包含TypeORM模块配置
|
||||
const typeOrmModule = module.imports?.find(imp =>
|
||||
imp && typeof imp === 'object' && 'module' in imp
|
||||
);
|
||||
expect(typeOrmModule).toBeDefined();
|
||||
});
|
||||
|
||||
it('should include CacheModule with correct configuration', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
// 检查是否包含缓存模块
|
||||
const cacheModule = module.imports?.find(imp =>
|
||||
imp && typeof imp === 'object' && 'module' in imp
|
||||
);
|
||||
expect(cacheModule).toBeDefined();
|
||||
});
|
||||
|
||||
it('should provide ZulipAccountsRepository', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
expect(module.providers).toContain(ZulipAccountsRepository);
|
||||
});
|
||||
|
||||
it('should provide AppLoggerService', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
expect(module.providers).toContain(AppLoggerService);
|
||||
});
|
||||
|
||||
it('should provide ZulipAccountsService with correct token', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
const serviceProvider = module.providers?.find(provider =>
|
||||
typeof provider === 'object' &&
|
||||
provider !== null &&
|
||||
'provide' in provider &&
|
||||
provider.provide === 'ZulipAccountsService'
|
||||
);
|
||||
|
||||
expect(serviceProvider).toBeDefined();
|
||||
expect((serviceProvider as any).useClass).toBe(ZulipAccountsService);
|
||||
});
|
||||
|
||||
it('should export all required services', () => {
|
||||
const module = ZulipAccountsModule.forDatabase();
|
||||
|
||||
expect(module.exports).toContain(ZulipAccountsRepository);
|
||||
expect(module.exports).toContain('ZulipAccountsService');
|
||||
expect(module.exports).toContain(TypeOrmModule);
|
||||
expect(module.exports).toContain(AppLoggerService);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forMemory()', () => {
|
||||
it('should create memory module with correct configuration', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
expect(module).toBeDefined();
|
||||
expect(module.module).toBe(ZulipAccountsModule);
|
||||
expect(module.imports).toHaveLength(1);
|
||||
expect(module.providers).toHaveLength(3);
|
||||
expect(module.exports).toHaveLength(3);
|
||||
});
|
||||
|
||||
it('should include CacheModule with memory-optimized configuration', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
// 检查是否包含缓存模块
|
||||
expect(module.imports).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should provide AppLoggerService', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
expect(module.providers).toContain(AppLoggerService);
|
||||
});
|
||||
|
||||
it('should provide ZulipAccountsRepository with memory implementation', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
const repositoryProvider = module.providers?.find(provider =>
|
||||
typeof provider === 'object' &&
|
||||
provider !== null &&
|
||||
'provide' in provider &&
|
||||
provider.provide === 'ZulipAccountsRepository'
|
||||
);
|
||||
|
||||
expect(repositoryProvider).toBeDefined();
|
||||
expect((repositoryProvider as any).useClass).toBe(ZulipAccountsMemoryRepository);
|
||||
});
|
||||
|
||||
it('should provide ZulipAccountsService with memory implementation', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
const serviceProvider = module.providers?.find(provider =>
|
||||
typeof provider === 'object' &&
|
||||
provider !== null &&
|
||||
'provide' in provider &&
|
||||
provider.provide === 'ZulipAccountsService'
|
||||
);
|
||||
|
||||
expect(serviceProvider).toBeDefined();
|
||||
expect((serviceProvider as any).useClass).toBe(ZulipAccountsMemoryService);
|
||||
});
|
||||
|
||||
it('should export memory-specific services', () => {
|
||||
const module = ZulipAccountsModule.forMemory();
|
||||
|
||||
expect(module.exports).toContain('ZulipAccountsRepository');
|
||||
expect(module.exports).toContain('ZulipAccountsService');
|
||||
expect(module.exports).toContain(AppLoggerService);
|
||||
expect(module.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forRoot()', () => {
|
||||
it('should return database module when all required env vars are set', () => {
|
||||
// 设置所有必需的环境变量
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
process.env[varName] = 'test_value';
|
||||
});
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 应该返回数据库模式的配置
|
||||
expect(module.imports).toHaveLength(2); // TypeORM + Cache
|
||||
expect(module.providers).toHaveLength(3);
|
||||
expect(module.exports).toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should return memory module when some required env vars are missing', () => {
|
||||
// 清除所有环境变量
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
delete process.env[varName];
|
||||
});
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 应该返回内存模式的配置
|
||||
expect(module.imports).toHaveLength(1); // 只有Cache
|
||||
expect(module.providers).toHaveLength(3);
|
||||
expect(module.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should return memory module when env vars are empty strings', () => {
|
||||
// 设置空字符串环境变量
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
process.env[varName] = '';
|
||||
});
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 应该返回内存模式的配置
|
||||
expect(module.imports).toHaveLength(1);
|
||||
expect(module.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should return database module when all env vars have valid values', () => {
|
||||
// 设置有效的环境变量值
|
||||
process.env.DB_HOST = 'localhost';
|
||||
process.env.DB_PORT = '3306';
|
||||
process.env.DB_USERNAME = 'user';
|
||||
process.env.DB_PASSWORD = 'password';
|
||||
process.env.DB_DATABASE = 'testdb';
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 应该返回数据库模式的配置
|
||||
expect(module.imports).toHaveLength(2);
|
||||
expect(module.exports).toContain(TypeOrmModule);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isDatabaseConfigured() function behavior', () => {
|
||||
it('should detect complete database configuration', () => {
|
||||
// 设置完整的数据库配置
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
process.env[varName] = 'valid_value';
|
||||
});
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 验证返回的是数据库模式
|
||||
expect(module.exports).toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should detect incomplete database configuration', () => {
|
||||
// 只设置部分环境变量
|
||||
if (REQUIRED_DB_ENV_VARS.length > 1) {
|
||||
process.env[REQUIRED_DB_ENV_VARS[0]] = 'value1';
|
||||
// 删除其他变量确保不完整
|
||||
for (let i = 1; i < REQUIRED_DB_ENV_VARS.length; i++) {
|
||||
delete process.env[REQUIRED_DB_ENV_VARS[i]];
|
||||
}
|
||||
}
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 验证返回的是内存模式
|
||||
expect(module.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should handle undefined environment variables', () => {
|
||||
// 确保所有必需变量都未定义
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
delete process.env[varName];
|
||||
});
|
||||
|
||||
const module = ZulipAccountsModule.forRoot();
|
||||
|
||||
// 验证返回的是内存模式
|
||||
expect(module.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
});
|
||||
|
||||
describe('module integration', () => {
|
||||
it('should create module configuration without errors', () => {
|
||||
expect(() => ZulipAccountsModule.forDatabase()).not.toThrow();
|
||||
expect(() => ZulipAccountsModule.forMemory()).not.toThrow();
|
||||
expect(() => ZulipAccountsModule.forRoot()).not.toThrow();
|
||||
});
|
||||
|
||||
it('should have different configurations for database and memory modes', () => {
|
||||
const databaseModule = ZulipAccountsModule.forDatabase();
|
||||
const memoryModule = ZulipAccountsModule.forMemory();
|
||||
|
||||
expect(databaseModule.imports?.length).toBeGreaterThan(memoryModule.imports?.length || 0);
|
||||
expect(databaseModule.exports).toContain(TypeOrmModule);
|
||||
expect(memoryModule.exports).not.toContain(TypeOrmModule);
|
||||
});
|
||||
|
||||
it('should provide consistent service interfaces', () => {
|
||||
const databaseModule = ZulipAccountsModule.forDatabase();
|
||||
const memoryModule = ZulipAccountsModule.forMemory();
|
||||
|
||||
expect(databaseModule.exports).toContain('ZulipAccountsService');
|
||||
expect(memoryModule.exports).toContain('ZulipAccountsService');
|
||||
expect(databaseModule.exports).toContain(AppLoggerService);
|
||||
expect(memoryModule.exports).toContain(AppLoggerService);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('should handle missing REQUIRED_DB_ENV_VARS constant gracefully', () => {
|
||||
// 这个测试确保即使常量有问题,模块仍能工作
|
||||
expect(() => {
|
||||
ZulipAccountsModule.forRoot();
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle null/undefined environment values', () => {
|
||||
// 设置一些环境变量为null(通过删除后重新设置)
|
||||
REQUIRED_DB_ENV_VARS.forEach(varName => {
|
||||
delete process.env[varName];
|
||||
(process.env as any)[varName] = null;
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
ZulipAccountsModule.forRoot();
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user