diff --git a/src/core/db/zulip_accounts/zulip_accounts.entity.spec.ts b/src/core/db/zulip_accounts/zulip_accounts.entity.spec.ts new file mode 100644 index 0000000..79ad23f --- /dev/null +++ b/src/core/db/zulip_accounts/zulip_accounts.entity.spec.ts @@ -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); + }); + }); +}); \ No newline at end of file diff --git a/src/core/db/zulip_accounts/zulip_accounts.module.spec.ts b/src/core/db/zulip_accounts/zulip_accounts.module.spec.ts new file mode 100644 index 0000000..b576062 --- /dev/null +++ b/src/core/db/zulip_accounts/zulip_accounts.module.spec.ts @@ -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(); + }); + }); +}); \ No newline at end of file