rhizome-node/__tests__/aggregation-resolvers.ts

542 lines
14 KiB
TypeScript

import {RhizomeNode} from "../src/node";
import {Lossless} from "../src/lossless";
import {Delta} from "../src/delta";
import {
AggregationResolver,
MinResolver,
MaxResolver,
SumResolver,
AverageResolver,
CountResolver,
AggregationType
} from "../src/aggregation-resolvers";
describe('Aggregation Resolvers', () => {
let node: RhizomeNode;
let lossless: Lossless;
beforeEach(() => {
node = new RhizomeNode();
lossless = new Lossless(node);
});
describe('Basic Aggregation', () => {
test('should aggregate numbers using min resolver', () => {
// Add first entity with score 10
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 10
}]
}));
// Add second entity with score 5
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity2",
targetContext: "score"
}, {
localContext: "score",
target: 5
}]
}));
// Add third entity with score 15
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity3",
targetContext: "score"
}, {
localContext: "score",
target: 15
}]
}));
const minResolver = new MinResolver(lossless, ['score']);
const result = minResolver.resolve();
expect(result).toBeDefined();
expect(Object.keys(result!)).toHaveLength(3);
expect(result!['entity1'].properties.score).toBe(10);
expect(result!['entity2'].properties.score).toBe(5);
expect(result!['entity3'].properties.score).toBe(15);
});
test('should aggregate numbers using max resolver', () => {
// Add deltas for entities
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 10
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity2",
targetContext: "score"
}, {
localContext: "score",
target: 5
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity3",
targetContext: "score"
}, {
localContext: "score",
target: 15
}]
}));
const maxResolver = new MaxResolver(lossless, ['score']);
const result = maxResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.score).toBe(10);
expect(result!['entity2'].properties.score).toBe(5);
expect(result!['entity3'].properties.score).toBe(15);
});
test('should aggregate numbers using sum resolver', () => {
// Add first value for entity1
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 10
}]
}));
// Add second value for entity1 (should sum)
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 20
}]
}));
// Add value for entity2
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity2",
targetContext: "value"
}, {
localContext: "value",
target: 5
}]
}));
const sumResolver = new SumResolver(lossless, ['value']);
const result = sumResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.value).toBe(30); // 10 + 20
expect(result!['entity2'].properties.value).toBe(5);
});
test('should aggregate numbers using average resolver', () => {
// Add multiple values for entity1
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 10
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 20
}]
}));
// Single value for entity2
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity2",
targetContext: "score"
}, {
localContext: "score",
target: 30
}]
}));
const avgResolver = new AverageResolver(lossless, ['score']);
const result = avgResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.score).toBe(15); // (10 + 20) / 2
expect(result!['entity2'].properties.score).toBe(30);
});
test('should count values using count resolver', () => {
// Add multiple visit deltas for entity1
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "visits"
}, {
localContext: "visits",
target: 1
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "visits"
}, {
localContext: "visits",
target: 1
}]
}));
// Single visit for entity2
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity2",
targetContext: "visits"
}, {
localContext: "visits",
target: 1
}]
}));
const countResolver = new CountResolver(lossless, ['visits']);
const result = countResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.visits).toBe(2); // count of 2 deltas
expect(result!['entity2'].properties.visits).toBe(1); // count of 1 delta
});
});
describe('Custom Aggregation Configuration', () => {
test('should handle mixed aggregation types', () => {
// Add first set of values
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "min_val"
}, {
localContext: "min_val",
target: 10
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "max_val"
}, {
localContext: "max_val",
target: 5
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "sum_val"
}, {
localContext: "sum_val",
target: 3
}]
}));
// Add second set of values
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "min_val"
}, {
localContext: "min_val",
target: 5
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "max_val"
}, {
localContext: "max_val",
target: 15
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "sum_val"
}, {
localContext: "sum_val",
target: 7
}]
}));
const resolver = new AggregationResolver(lossless, {
min_val: 'min' as AggregationType,
max_val: 'max' as AggregationType,
sum_val: 'sum' as AggregationType
});
const result = resolver.resolve();
expect(result).toBeDefined();
const entity = result!['entity1'];
expect(entity.properties.min_val).toBe(5); // min of 10, 5
expect(entity.properties.max_val).toBe(15); // max of 5, 15
expect(entity.properties.sum_val).toBe(10); // sum of 3, 7
});
test('should ignore non-numeric values', () => {
// Add numeric value
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 10
}]
}));
// Add non-numeric value (string)
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "name"
}, {
localContext: "name",
target: 'test'
}]
}));
// Add another numeric value
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "score"
}, {
localContext: "score",
target: 20
}]
}));
const sumResolver = new SumResolver(lossless, ['score', 'name']);
const result = sumResolver.resolve();
expect(result).toBeDefined();
const entity = result!['entity1'];
expect(entity.properties.score).toBe(30); // sum of 10, 20
expect(entity.properties.name).toBe(0); // ignored non-numeric, defaults to 0
});
test('should handle empty value arrays', () => {
// Create entity with non-aggregated property
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "name"
}, {
localContext: "name",
target: 'test'
}]
}));
const sumResolver = new SumResolver(lossless, ['score']);
const result = sumResolver.resolve();
expect(result).toBeDefined();
// Should not have entity1 since no 'score' property was found
expect(result!['entity1']).toBeUndefined();
});
});
describe('Edge Cases', () => {
test('should handle single value aggregations', () => {
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 42
}]
}));
const avgResolver = new AverageResolver(lossless, ['value']);
const result = avgResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.value).toBe(42);
});
test('should handle zero values', () => {
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 0
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 10
}]
}));
const sumResolver = new SumResolver(lossless, ['value']);
const result = sumResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.value).toBe(10); // 0 + 10
});
test('should handle negative values', () => {
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: -5
}]
}));
lossless.ingestDelta(new Delta({
creator: 'test',
host: 'host1',
pointers: [{
localContext: "collection",
target: "entity1",
targetContext: "value"
}, {
localContext: "value",
target: 10
}]
}));
const minResolver = new MinResolver(lossless, ['value']);
const result = minResolver.resolve();
expect(result).toBeDefined();
expect(result!['entity1'].properties.value).toBe(-5);
});
});
});