godcrm/backend/utils/__tests__/formulaEvaluator.test.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

207 lines
6.3 KiB
JavaScript

/**
* Formula Evaluator Tests - TDD Approach (ADR-026)
*
* 🔴 RED → 🟢 GREEN → 🔵 REFACTOR
*/
import { describe, test, expect } from 'vitest';
import {
evaluateFormula,
evaluateAggregation,
FORMULA_FUNCTIONS
} from '../formulaEvaluator.js';
describe('Formula Evaluator (ADR-026)', () => {
// ============================================================
// PART 1: Basic Arithmetic
// ============================================================
describe('evaluateFormula() - Basic Arithmetic', () => {
test('should evaluate simple number', () => {
expect(evaluateFormula('42')).toBe(42);
});
test('should evaluate decimal number', () => {
expect(evaluateFormula('0.20')).toBe(0.20);
});
test('should evaluate addition', () => {
expect(evaluateFormula('10 + 5')).toBe(15);
});
test('should evaluate subtraction', () => {
expect(evaluateFormula('10 - 3')).toBe(7);
});
test('should evaluate multiplication', () => {
expect(evaluateFormula('6 * 7')).toBe(42);
});
test('should evaluate division', () => {
expect(evaluateFormula('100 / 4')).toBe(25);
});
test('should evaluate parentheses', () => {
expect(evaluateFormula('(10 + 5) * 2')).toBe(30);
});
test('should handle negative numbers', () => {
expect(evaluateFormula('-5 + 10')).toBe(5);
});
});
// ============================================================
// PART 2: Variables substitution
// ============================================================
describe('evaluateFormula() - Variable substitution', () => {
test('should substitute $variable with value', () => {
const context = { variables: { $tax_rate: 0.20 } };
expect(evaluateFormula('100 * $tax_rate', context)).toBe(20);
});
test('should handle multiple variables', () => {
const context = {
variables: {
$price: 100,
$tax_rate: 0.20,
$discount: 10
}
};
expect(evaluateFormula('$price * $tax_rate - $discount', context)).toBe(10);
});
test('should return error for undefined variable', () => {
expect(() => evaluateFormula('$undefined_var')).toThrow();
});
});
// ============================================================
// PART 3: Column references
// ============================================================
describe('evaluateFormula() - Column references', () => {
test('should substitute {{column}} with row value', () => {
const context = {
row: { amount: 150, quantity: 3 }
};
expect(evaluateFormula('{{amount}} * {{quantity}}', context)).toBe(450);
});
test('should handle column with spaces in name', () => {
const context = {
row: { 'Total Amount': 200 }
};
expect(evaluateFormula('{{Total Amount}} * 2', context)).toBe(400);
});
});
// ============================================================
// PART 4: Aggregation functions
// ============================================================
describe('evaluateAggregation()', () => {
const testData = [10, 20, 30, 40, 50];
test('SUM should return sum of values', () => {
expect(evaluateAggregation('SUM', testData)).toBe(150);
});
test('AVG should return average of values', () => {
expect(evaluateAggregation('AVG', testData)).toBe(30);
});
test('MIN should return minimum value', () => {
expect(evaluateAggregation('MIN', testData)).toBe(10);
});
test('MAX should return maximum value', () => {
expect(evaluateAggregation('MAX', testData)).toBe(50);
});
test('COUNT should return count of values', () => {
expect(evaluateAggregation('COUNT', testData)).toBe(5);
});
test('COUNT_EMPTY should count null/undefined values', () => {
const data = [10, null, 20, undefined, 30, ''];
expect(evaluateAggregation('COUNT_EMPTY', data)).toBe(3);
});
test('COUNT_NOT_EMPTY should count non-null values', () => {
const data = [10, null, 20, undefined, 30, ''];
expect(evaluateAggregation('COUNT_NOT_EMPTY', data)).toBe(3);
});
test('SUM should return 0 for empty array', () => {
expect(evaluateAggregation('SUM', [])).toBe(0);
});
test('AVG should return 0 for empty array', () => {
expect(evaluateAggregation('AVG', [])).toBe(0);
});
});
// ============================================================
// PART 5: Built-in functions
// ============================================================
describe('FORMULA_FUNCTIONS', () => {
test('IF should return true value when condition is true', () => {
const context = { row: { status: 'completed' } };
expect(evaluateFormula('IF({{status}} == "completed", 100, 0)', context)).toBe(100);
});
test('IF should return false value when condition is false', () => {
const context = { row: { status: 'pending' } };
expect(evaluateFormula('IF({{status}} == "completed", 100, 0)', context)).toBe(0);
});
test('ROUND should round to specified decimals', () => {
expect(evaluateFormula('ROUND(3.14159, 2)')).toBe(3.14);
});
test('FLOOR should round down', () => {
expect(evaluateFormula('FLOOR(3.9)')).toBe(3);
});
test('CEIL should round up', () => {
expect(evaluateFormula('CEIL(3.1)')).toBe(4);
});
test('ABS should return absolute value', () => {
expect(evaluateFormula('ABS(-42)')).toBe(42);
});
test('IFERROR should return value when no error', () => {
expect(evaluateFormula('IFERROR(10 / 2, 0)')).toBe(5);
});
test('IFERROR should return fallback on error', () => {
expect(evaluateFormula('IFERROR(10 / 0, 0)')).toBe(0);
});
});
// ============================================================
// PART 6: Edge cases
// ============================================================
describe('Edge cases', () => {
test('should handle division by zero', () => {
expect(evaluateFormula('IFERROR(10 / 0, 0)')).toBe(0);
});
test('should handle empty formula', () => {
expect(evaluateFormula('')).toBe(0);
});
test('should handle whitespace-only formula', () => {
expect(evaluateFormula(' ')).toBe(0);
});
test('should handle string values', () => {
expect(evaluateFormula('"hello"')).toBe('hello');
});
});
});