532 lines
16 KiB
TypeScript
532 lines
16 KiB
TypeScript
import { describe, beforeEach, afterEach, it, expect, jest } from '@jest/globals';
|
|
import { DebrosFramework, DebrosFrameworkConfig } from '../../src/framework/DebrosFramework';
|
|
import { BaseModel } from '../../src/framework/models/BaseModel';
|
|
import { Model, Field, HasMany, BelongsTo } from '../../src/framework/models/decorators';
|
|
import { createMockServices } from '../mocks/services';
|
|
|
|
// Test models for integration testing
|
|
@Model({
|
|
scope: 'global',
|
|
type: 'docstore'
|
|
})
|
|
class User extends BaseModel {
|
|
@Field({ type: 'string', required: true })
|
|
username: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
email: string;
|
|
|
|
@Field({ type: 'boolean', required: false, default: true })
|
|
isActive: boolean;
|
|
|
|
@HasMany(() => Post, 'userId')
|
|
posts: Post[];
|
|
}
|
|
|
|
@Model({
|
|
scope: 'user',
|
|
type: 'docstore'
|
|
})
|
|
class Post extends BaseModel {
|
|
@Field({ type: 'string', required: true })
|
|
title: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
content: string;
|
|
|
|
@Field({ type: 'string', required: true })
|
|
userId: string;
|
|
|
|
@Field({ type: 'boolean', required: false, default: false })
|
|
published: boolean;
|
|
|
|
@BelongsTo(() => User, 'userId')
|
|
user: User;
|
|
}
|
|
|
|
describe('DebrosFramework Integration Tests', () => {
|
|
let framework: DebrosFramework;
|
|
let mockServices: any;
|
|
let config: DebrosFrameworkConfig;
|
|
|
|
beforeEach(() => {
|
|
mockServices = createMockServices();
|
|
|
|
config = {
|
|
environment: 'test',
|
|
features: {
|
|
autoMigration: false,
|
|
automaticPinning: false,
|
|
pubsub: false,
|
|
queryCache: true,
|
|
relationshipCache: true
|
|
},
|
|
performance: {
|
|
queryTimeout: 5000,
|
|
migrationTimeout: 30000,
|
|
maxConcurrentOperations: 10,
|
|
batchSize: 100
|
|
},
|
|
monitoring: {
|
|
enableMetrics: true,
|
|
logLevel: 'info',
|
|
metricsInterval: 1000
|
|
}
|
|
};
|
|
|
|
framework = new DebrosFramework(config);
|
|
|
|
// Suppress console output for cleaner test output
|
|
jest.spyOn(console, 'log').mockImplementation();
|
|
jest.spyOn(console, 'error').mockImplementation();
|
|
jest.spyOn(console, 'warn').mockImplementation();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
if (framework) {
|
|
await framework.cleanup();
|
|
}
|
|
jest.restoreAllMocks();
|
|
});
|
|
|
|
describe('Framework Initialization', () => {
|
|
it('should initialize successfully with valid services', async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
|
|
const status = framework.getStatus();
|
|
expect(status.initialized).toBe(true);
|
|
expect(status.healthy).toBe(true);
|
|
expect(status.environment).toBe('test');
|
|
expect(status.services.orbitdb).toBe('connected');
|
|
expect(status.services.ipfs).toBe('connected');
|
|
});
|
|
|
|
it('should throw error when already initialized', async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
|
|
await expect(
|
|
framework.initialize(mockServices.orbitDBService, mockServices.ipfsService)
|
|
).rejects.toThrow('Framework is already initialized');
|
|
});
|
|
|
|
it('should throw error without required services', async () => {
|
|
await expect(framework.initialize()).rejects.toThrow(
|
|
'IPFS service is required'
|
|
);
|
|
});
|
|
|
|
it('should handle initialization failures gracefully', async () => {
|
|
// Make IPFS service initialization fail
|
|
const failingIPFS = {
|
|
...mockServices.ipfsService,
|
|
init: jest.fn().mockRejectedValue(new Error('IPFS init failed'))
|
|
};
|
|
|
|
await expect(
|
|
framework.initialize(mockServices.orbitDBService, failingIPFS)
|
|
).rejects.toThrow('IPFS init failed');
|
|
|
|
const status = framework.getStatus();
|
|
expect(status.initialized).toBe(false);
|
|
expect(status.healthy).toBe(false);
|
|
});
|
|
|
|
it('should apply config overrides during initialization', async () => {
|
|
const overrideConfig = {
|
|
environment: 'production' as const,
|
|
features: { queryCache: false }
|
|
};
|
|
|
|
await framework.initialize(
|
|
mockServices.orbitDBService,
|
|
mockServices.ipfsService,
|
|
overrideConfig
|
|
);
|
|
|
|
const status = framework.getStatus();
|
|
expect(status.environment).toBe('production');
|
|
});
|
|
});
|
|
|
|
describe('Framework Lifecycle', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should provide access to core managers', () => {
|
|
expect(framework.getDatabaseManager()).toBeDefined();
|
|
expect(framework.getShardManager()).toBeDefined();
|
|
expect(framework.getRelationshipManager()).toBeDefined();
|
|
expect(framework.getQueryCache()).toBeDefined();
|
|
});
|
|
|
|
it('should provide access to services', () => {
|
|
expect(framework.getOrbitDBService()).toBeDefined();
|
|
expect(framework.getIPFSService()).toBeDefined();
|
|
});
|
|
|
|
it('should handle graceful shutdown', async () => {
|
|
const initialStatus = framework.getStatus();
|
|
expect(initialStatus.initialized).toBe(true);
|
|
|
|
await framework.stop();
|
|
|
|
const finalStatus = framework.getStatus();
|
|
expect(finalStatus.initialized).toBe(false);
|
|
});
|
|
|
|
it('should perform health checks', async () => {
|
|
const health = await framework.healthCheck();
|
|
|
|
expect(health.healthy).toBe(true);
|
|
expect(health.services.ipfs).toBe('connected');
|
|
expect(health.services.orbitdb).toBe('connected');
|
|
expect(health.lastCheck).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('should collect metrics', () => {
|
|
const metrics = framework.getMetrics();
|
|
|
|
expect(metrics).toHaveProperty('uptime');
|
|
expect(metrics).toHaveProperty('totalModels');
|
|
expect(metrics).toHaveProperty('totalDatabases');
|
|
expect(metrics).toHaveProperty('queriesExecuted');
|
|
expect(metrics).toHaveProperty('memoryUsage');
|
|
expect(metrics).toHaveProperty('performance');
|
|
});
|
|
});
|
|
|
|
describe('Model and Database Integration', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should integrate with model system for database operations', async () => {
|
|
// Create a user
|
|
const userData = {
|
|
username: 'testuser',
|
|
email: 'test@example.com',
|
|
isActive: true
|
|
};
|
|
|
|
const user = await User.create(userData);
|
|
|
|
expect(user).toBeInstanceOf(User);
|
|
expect(user.username).toBe('testuser');
|
|
expect(user.email).toBe('test@example.com');
|
|
expect(user.isActive).toBe(true);
|
|
expect(user.id).toBeDefined();
|
|
});
|
|
|
|
it('should handle user-scoped and global-scoped models differently', async () => {
|
|
// Global-scoped model (User)
|
|
const user = await User.create({
|
|
username: 'globaluser',
|
|
email: 'global@example.com'
|
|
});
|
|
|
|
// User-scoped model (Post) - should use user's database
|
|
const post = await Post.create({
|
|
title: 'Test Post',
|
|
content: 'This is a test post',
|
|
userId: user.id,
|
|
published: true
|
|
});
|
|
|
|
expect(user).toBeInstanceOf(User);
|
|
expect(post).toBeInstanceOf(Post);
|
|
expect(post.userId).toBe(user.id);
|
|
});
|
|
|
|
it('should support relationship loading', async () => {
|
|
const user = await User.create({
|
|
username: 'userWithPosts',
|
|
email: 'posts@example.com'
|
|
});
|
|
|
|
// Create posts for the user
|
|
await Post.create({
|
|
title: 'First Post',
|
|
content: 'Content 1',
|
|
userId: user.id
|
|
});
|
|
|
|
await Post.create({
|
|
title: 'Second Post',
|
|
content: 'Content 2',
|
|
userId: user.id
|
|
});
|
|
|
|
// Load user's posts
|
|
const relationshipManager = framework.getRelationshipManager();
|
|
const posts = await relationshipManager!.loadRelationship(user, 'posts');
|
|
|
|
expect(Array.isArray(posts)).toBe(true);
|
|
expect(posts.length).toBeGreaterThanOrEqual(0); // Mock may return empty array
|
|
});
|
|
});
|
|
|
|
describe('Query and Cache Integration', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should integrate query system with cache', async () => {
|
|
const queryCache = framework.getQueryCache();
|
|
expect(queryCache).toBeDefined();
|
|
|
|
// Just verify that the cache exists and has basic functionality
|
|
expect(typeof queryCache!.set).toBe('function');
|
|
expect(typeof queryCache!.get).toBe('function');
|
|
expect(typeof queryCache!.clear).toBe('function');
|
|
});
|
|
|
|
it('should support complex query building', () => {
|
|
const query = User.query()
|
|
.where('isActive', true)
|
|
.where('email', 'like', '%@example.com')
|
|
.orderBy('username', 'asc')
|
|
.limit(10);
|
|
|
|
expect(query).toBeDefined();
|
|
expect(typeof query.find).toBe('function');
|
|
expect(typeof query.count).toBe('function');
|
|
});
|
|
});
|
|
|
|
describe('Sharding Integration', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should integrate with shard manager for model distribution', () => {
|
|
const shardManager = framework.getShardManager();
|
|
expect(shardManager).toBeDefined();
|
|
|
|
// Test shard routing
|
|
const testKey = 'test-key-123';
|
|
const modelWithShards = 'TestModel';
|
|
|
|
// This would work if we had shards created for TestModel
|
|
expect(() => {
|
|
shardManager!.getShardCount(modelWithShards);
|
|
}).not.toThrow();
|
|
});
|
|
|
|
it('should support cross-shard queries', async () => {
|
|
const shardManager = framework.getShardManager();
|
|
|
|
// Test querying across all shards (mock implementation)
|
|
const queryFn = async (database: any) => {
|
|
return []; // Mock query result
|
|
};
|
|
|
|
// This would work if we had shards created
|
|
const models = shardManager!.getAllModelsWithShards();
|
|
expect(Array.isArray(models)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Migration Integration', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should integrate migration system', () => {
|
|
const migrationManager = framework.getMigrationManager();
|
|
expect(migrationManager).toBeDefined();
|
|
|
|
// Test migration registration
|
|
const testMigration = {
|
|
id: 'test-migration-1',
|
|
version: '1.0.0',
|
|
name: 'Test Migration',
|
|
description: 'A test migration',
|
|
targetModels: ['User'],
|
|
up: [{
|
|
type: 'add_field' as const,
|
|
modelName: 'User',
|
|
fieldName: 'newField',
|
|
fieldConfig: { type: 'string' as const, required: false }
|
|
}],
|
|
down: [{
|
|
type: 'remove_field' as const,
|
|
modelName: 'User',
|
|
fieldName: 'newField'
|
|
}],
|
|
createdAt: Date.now()
|
|
};
|
|
|
|
expect(() => {
|
|
migrationManager!.registerMigration(testMigration);
|
|
}).not.toThrow();
|
|
|
|
const registered = migrationManager!.getMigration(testMigration.id);
|
|
expect(registered).toEqual(testMigration);
|
|
});
|
|
|
|
it('should handle pending migrations', () => {
|
|
const migrationManager = framework.getMigrationManager();
|
|
|
|
const pendingMigrations = migrationManager!.getPendingMigrations();
|
|
expect(Array.isArray(pendingMigrations)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Error Handling and Recovery', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should handle service failures gracefully', async () => {
|
|
// Simulate OrbitDB service failure
|
|
const orbitDBService = framework.getOrbitDBService();
|
|
jest.spyOn(orbitDBService!, 'getOrbitDB').mockImplementation(() => {
|
|
throw new Error('OrbitDB service failed');
|
|
});
|
|
|
|
// Framework should still respond to health checks
|
|
const health = await framework.healthCheck();
|
|
expect(health).toBeDefined();
|
|
});
|
|
|
|
it('should provide error information in status', async () => {
|
|
const status = framework.getStatus();
|
|
|
|
expect(status).toHaveProperty('services');
|
|
expect(status.services).toHaveProperty('orbitdb');
|
|
expect(status.services).toHaveProperty('ipfs');
|
|
});
|
|
|
|
it('should support manual service recovery', async () => {
|
|
// Stop the framework
|
|
await framework.stop();
|
|
|
|
// Verify it's stopped
|
|
let status = framework.getStatus();
|
|
expect(status.initialized).toBe(false);
|
|
|
|
// Restart with new services
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
|
|
// Verify it's running again
|
|
status = framework.getStatus();
|
|
expect(status.initialized).toBe(true);
|
|
expect(status.healthy).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('Configuration Management', () => {
|
|
it('should merge default configuration correctly', () => {
|
|
const customConfig: DebrosFrameworkConfig = {
|
|
environment: 'production',
|
|
features: {
|
|
queryCache: false,
|
|
automaticPinning: true
|
|
},
|
|
performance: {
|
|
batchSize: 500
|
|
}
|
|
};
|
|
|
|
const customFramework = new DebrosFramework(customConfig);
|
|
const status = customFramework.getStatus();
|
|
|
|
expect(status.environment).toBe('production');
|
|
});
|
|
|
|
it('should support configuration updates', async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
|
|
const configManager = framework.getConfigManager();
|
|
expect(configManager).toBeDefined();
|
|
|
|
// Configuration should be accessible through the framework
|
|
const currentConfig = configManager!.getFullConfig();
|
|
expect(currentConfig).toBeDefined();
|
|
expect(currentConfig.environment).toBe('test');
|
|
});
|
|
});
|
|
|
|
describe('Performance and Monitoring', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should track uptime correctly', () => {
|
|
const metrics = framework.getMetrics();
|
|
expect(metrics.uptime).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
it('should collect performance metrics', () => {
|
|
const metrics = framework.getMetrics();
|
|
|
|
expect(metrics.performance).toBeDefined();
|
|
expect(metrics.performance.slowQueries).toBeDefined();
|
|
expect(metrics.performance.failedOperations).toBeDefined();
|
|
expect(metrics.performance.averageResponseTime).toBeDefined();
|
|
});
|
|
|
|
it('should track memory usage', () => {
|
|
const metrics = framework.getMetrics();
|
|
|
|
expect(metrics.memoryUsage).toBeDefined();
|
|
expect(metrics.memoryUsage.queryCache).toBeDefined();
|
|
expect(metrics.memoryUsage.relationshipCache).toBeDefined();
|
|
expect(metrics.memoryUsage.total).toBeDefined();
|
|
});
|
|
|
|
it('should provide detailed status information', () => {
|
|
const status = framework.getStatus();
|
|
|
|
expect(status.version).toBeDefined();
|
|
expect(status.lastHealthCheck).toBeGreaterThanOrEqual(0);
|
|
expect(status.services).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('Concurrent Operations', () => {
|
|
beforeEach(async () => {
|
|
await framework.initialize(mockServices.orbitDBService, mockServices.ipfsService);
|
|
});
|
|
|
|
it('should handle concurrent model operations', async () => {
|
|
const promises = [];
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
promises.push(User.create({
|
|
username: `user${i}`,
|
|
email: `user${i}@example.com`
|
|
}));
|
|
}
|
|
|
|
const users = await Promise.all(promises);
|
|
|
|
expect(users).toHaveLength(5);
|
|
users.forEach((user, index) => {
|
|
expect(user.username).toBe(`user${index}`);
|
|
});
|
|
});
|
|
|
|
it('should handle concurrent relationship loading', async () => {
|
|
const user = await User.create({
|
|
username: 'concurrentUser',
|
|
email: 'concurrent@example.com'
|
|
});
|
|
|
|
const relationshipManager = framework.getRelationshipManager();
|
|
|
|
const promises = [
|
|
relationshipManager!.loadRelationship(user, 'posts'),
|
|
relationshipManager!.loadRelationship(user, 'posts'),
|
|
relationshipManager!.loadRelationship(user, 'posts')
|
|
];
|
|
|
|
const results = await Promise.all(promises);
|
|
|
|
expect(results).toHaveLength(3);
|
|
// Results should be consistent (either all arrays or all same result)
|
|
expect(Array.isArray(results[0])).toBe(Array.isArray(results[1]));
|
|
});
|
|
});
|
|
}); |