Core Changes: - Completely rewrote CustomResolver reducer with dependency-ordered processing - Enhanced plugin initialization with proper dependency injection - Improved delta processing and property value tracking - Added robust error handling for duplicate property IDs Resolver Improvements: - Updated to use new accumulator structure - Implemented execution order processing for plugins - Enhanced debug logging and error reporting - Simplified TimestampResolver by removing unused initializer Configuration Updates: - Added TypeScript path aliases for test helpers - Improved module resolution paths Key Benefits: - More robust plugin dependency management - More efficient state updates - Enhanced type safety - Better error messages and debugging - More consistent plugin initialization This refactoring focuses on improving the robustness of the resolver, especially around plugin lifecycle management and dependency handling. The changes ensure better separation of concerns and more predictable behavior when dealing with complex plugin dependencies.
206 lines
7.6 KiB
TypeScript
206 lines
7.6 KiB
TypeScript
/**
|
|
* Tests for lossless view compose() and decompose() bidirectional conversion
|
|
* Ensures that deltas can be composed into lossless views and decomposed back
|
|
* to the original deltas with all pointer relationships preserved.
|
|
*/
|
|
|
|
import { RhizomeNode } from '@src/node';
|
|
import { createDelta } from '@src/core/delta-builder';
|
|
|
|
describe('Lossless View Compose/Decompose', () => {
|
|
let node: RhizomeNode;
|
|
|
|
beforeEach(() => {
|
|
node = new RhizomeNode();
|
|
});
|
|
|
|
describe('Bidirectional Conversion', () => {
|
|
test('should compose and decompose simple entity deltas correctly', () => {
|
|
// Create simple entity deltas
|
|
const nameDeltas = [
|
|
createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice Smith')
|
|
.buildV1(),
|
|
createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'email')
|
|
.addPointer('email', 'alice@example.com')
|
|
.buildV1()
|
|
];
|
|
|
|
// Ingest the deltas
|
|
nameDeltas.forEach(delta => node.lossless.ingestDelta(delta));
|
|
|
|
// Compose lossless view
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
expect(aliceView).toBeDefined();
|
|
expect(aliceView.id).toBe('alice');
|
|
expect(aliceView.propertyDeltas.name).toHaveLength(1);
|
|
expect(aliceView.propertyDeltas.email).toHaveLength(1);
|
|
|
|
// Decompose back to deltas
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
expect(decomposed).toHaveLength(2);
|
|
|
|
// Check that original deltas are preserved
|
|
const originalIds = nameDeltas.map(d => d.id).sort();
|
|
const decomposedIds = decomposed.map(d => d.id).sort();
|
|
expect(decomposedIds).toEqual(originalIds);
|
|
|
|
// Verify pointer structure is preserved
|
|
const nameDataDelta = decomposed.find(d =>
|
|
d.pointers.some(p => p.localContext === 'name' && p.target === 'Alice Smith')
|
|
);
|
|
expect(nameDataDelta).toBeDefined();
|
|
expect(nameDataDelta?.pointers).toHaveLength(2);
|
|
|
|
const upPointer = nameDataDelta?.pointers.find(p => p.targetContext === 'name');
|
|
expect(upPointer).toBeDefined();
|
|
expect(upPointer?.target).toBe('alice');
|
|
expect(upPointer?.localContext).toBe('users');
|
|
});
|
|
|
|
test('should handle multi-pointer relationship deltas correctly', () => {
|
|
// Create a complex relationship delta
|
|
const relationshipDelta = createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'relationships')
|
|
.addPointer('partner', 'bob')
|
|
.addPointer('type', 'friendship')
|
|
.addPointer('since', '2020-01-15')
|
|
.addPointer('intensity', 8)
|
|
.buildV1();
|
|
|
|
node.lossless.ingestDelta(relationshipDelta);
|
|
|
|
// Compose and decompose
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
expect(decomposed).toHaveLength(1);
|
|
const reconstituted = decomposed[0];
|
|
|
|
// Should have all 5 pointers
|
|
expect(reconstituted.pointers).toHaveLength(5);
|
|
|
|
// Check that all pointer types are preserved
|
|
const contexts = reconstituted.pointers.map(p => p.localContext).sort();
|
|
expect(contexts).toEqual(['users', 'partner', 'type', 'since', 'intensity'].sort());
|
|
|
|
// Check that the "up" pointer to alice is correctly reconstructed
|
|
const upPointer = reconstituted.pointers.find(p => p.targetContext === 'relationships');
|
|
expect(upPointer).toBeDefined();
|
|
expect(upPointer?.target).toBe('alice');
|
|
expect(upPointer?.localContext).toBe('users');
|
|
|
|
// Check scalar values are preserved
|
|
const intensityPointer = reconstituted.pointers.find(p => p.localContext === 'intensity');
|
|
expect(intensityPointer?.target).toBe(8);
|
|
});
|
|
|
|
test('should handle reference relationships correctly', () => {
|
|
// Create entities first
|
|
const aliceDelta = createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice')
|
|
.buildV1();
|
|
|
|
const bobDelta = createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'bob', 'name')
|
|
.addPointer('name', 'Bob')
|
|
.buildV1();
|
|
|
|
// Create friendship relationship
|
|
const friendshipDelta = createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'friends')
|
|
.addPointer('friend', 'bob', 'friends')
|
|
.buildV1();
|
|
|
|
[aliceDelta, bobDelta, friendshipDelta].forEach(d => node.lossless.ingestDelta(d));
|
|
|
|
// Compose Alice's view
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
expect(aliceView.propertyDeltas.friends).toHaveLength(1);
|
|
|
|
// Decompose and verify the friendship delta is correctly reconstructed
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
const friendshipReconstituted = decomposed.find(d =>
|
|
d.pointers.some(p => p.localContext === 'friend')
|
|
);
|
|
|
|
expect(friendshipReconstituted).toBeDefined();
|
|
expect(friendshipReconstituted?.pointers).toHaveLength(2);
|
|
|
|
// Check both reference pointers are preserved
|
|
const alicePointer = friendshipReconstituted?.pointers.find(p => p.target === 'alice');
|
|
const bobPointer = friendshipReconstituted?.pointers.find(p => p.target === 'bob');
|
|
|
|
expect(alicePointer).toBeDefined();
|
|
expect(alicePointer?.targetContext).toBe('friends');
|
|
expect(bobPointer).toBeDefined();
|
|
expect(bobPointer?.targetContext).toBe('friends');
|
|
});
|
|
|
|
test('should preserve delta metadata correctly', () => {
|
|
const originalDelta = createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice')
|
|
.buildV1();
|
|
|
|
node.lossless.ingestDelta(originalDelta);
|
|
|
|
const composed = node.lossless.compose(['alice']);
|
|
const decomposed = node.lossless.decompose(composed['alice']);
|
|
|
|
expect(decomposed).toHaveLength(1);
|
|
const reconstituted = decomposed[0];
|
|
|
|
// Check metadata preservation
|
|
expect(reconstituted.id).toBe(originalDelta.id);
|
|
expect(reconstituted.creator).toBe(originalDelta.creator);
|
|
expect(reconstituted.host).toBe(originalDelta.host);
|
|
expect(reconstituted.timeCreated).toBe(originalDelta.timeCreated);
|
|
});
|
|
|
|
test('should handle multiple deltas for the same property', () => {
|
|
// Create multiple name changes for alice
|
|
const nameDeltas = [
|
|
createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice')
|
|
.buildV1(),
|
|
createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice Smith')
|
|
.buildV1(),
|
|
createDelta('test-creator', 'test-host')
|
|
.addPointer('users', 'alice', 'name')
|
|
.addPointer('name', 'Alice Johnson')
|
|
.buildV1()
|
|
];
|
|
|
|
nameDeltas.forEach(d => node.lossless.ingestDelta(d));
|
|
|
|
const composed = node.lossless.compose(['alice']);
|
|
const aliceView = composed['alice'];
|
|
|
|
// Should have 3 deltas for the name property
|
|
expect(aliceView.propertyDeltas.name).toHaveLength(3);
|
|
|
|
const decomposed = node.lossless.decompose(aliceView);
|
|
|
|
// Should decompose back to 3 separate deltas
|
|
expect(decomposed).toHaveLength(3);
|
|
|
|
// All original delta IDs should be preserved
|
|
const originalIds = nameDeltas.map(d => d.id).sort();
|
|
const decomposedIds = decomposed.map(d => d.id).sort();
|
|
expect(decomposedIds).toEqual(originalIds);
|
|
});
|
|
});
|
|
}); |