mirror of
https://github.com/proffesor-for-testing/agentic-qe.git
synced 2026-09-19 08:45:47 +08:00
2253d05618
Remove the nested v3/ subdirectory by moving all active source code, tests, configs, and assets directly to the project root. Archive v2/ (dead code, 847MB) to v2-archive.tar.gz. Key changes: - Move v3/src/ → src/, v3/tests/ → tests/, v3/packages/ → packages/ - Move v3/tsconfig.json, v3/vitest.config.ts to root - Merge v3/package.json into root package.json (ESM, scripts, deps, exports) - Merge v3/scripts/ into scripts/, v3/implementation/ into docs/implementation/ - Update all CI workflows to remove "cd v3 &&" and v3/ path prefixes - Fix all init installer path resolution (3-level-up → 2-level-up) - Fix hooks to use "node ./dist/cli/bundle.js" instead of bare "aqe" - Update settings.json ADR/DDD directories to docs/implementation/ - Fix helper scripts (statusline, adr-compliance, ddd-tracker, guidance-hooks) - Update 58 agent definitions: aqe/v3/ → aqe/ memory namespaces - Update 15 guidance shards: v3/src/ → src/ path references - Fix sync interfaces: remove v3/ and v2/ path references - Fix test path calculations (process.cwd() + '..' no longer needed) - Fix flaky perf test threshold (500ms → 1000ms for CI environments) - Fix complexity-analyzer test to match ADR-051 keyword-based eligibility Build passes, 17,901 tests pass, CLI and MCP bundles verified. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
247 lines
6.7 KiB
TypeScript
247 lines
6.7 KiB
TypeScript
/**
|
|
* G-Counter Unit Tests
|
|
*
|
|
* Tests for the Grow-only Counter CRDT implementation.
|
|
* Verifies CRDT properties: convergence, commutativity, idempotency.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
|
|
import {
|
|
createGCounter,
|
|
createGCounterFromState,
|
|
isGCounterState,
|
|
getNodeCountFromState,
|
|
getContributingNodes,
|
|
} from '../../../src/memory/crdt/g-counter.js';
|
|
|
|
describe('G-Counter', () => {
|
|
describe('increment and value', () => {
|
|
it('should start at zero', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
|
|
// Act
|
|
const value = counter.get();
|
|
|
|
// Assert
|
|
expect(value).toBe(0);
|
|
});
|
|
|
|
it('should increment by 1 by default', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
|
|
// Act
|
|
counter.increment();
|
|
|
|
// Assert
|
|
expect(counter.get()).toBe(1);
|
|
});
|
|
|
|
it('should increment by arbitrary positive value', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
|
|
// Act
|
|
counter.increment(42);
|
|
|
|
// Assert
|
|
expect(counter.get()).toBe(42);
|
|
});
|
|
|
|
it('should accumulate multiple increments', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
|
|
// Act
|
|
counter.increment(3);
|
|
counter.increment(7);
|
|
counter.increment(5);
|
|
|
|
// Assert
|
|
expect(counter.get()).toBe(15);
|
|
expect(counter.getLocalCount()).toBe(15);
|
|
});
|
|
|
|
it('should reject negative increment values', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
|
|
// Act & Assert
|
|
expect(() => counter.increment(-1)).toThrow('G-Counter can only increment by positive values');
|
|
});
|
|
|
|
it('should no-op on zero increment', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
counter.increment(5);
|
|
const stateBefore = counter.getState();
|
|
|
|
// Act
|
|
counter.increment(0);
|
|
|
|
// Assert
|
|
expect(counter.get()).toBe(5);
|
|
expect(counter.getState().version).toBe(stateBefore.version);
|
|
});
|
|
});
|
|
|
|
describe('convergence after merge', () => {
|
|
it('should converge two replicas with independent increments to same value', () => {
|
|
// Arrange
|
|
const replicaA = createGCounter('node-a');
|
|
const replicaB = createGCounter('node-b');
|
|
replicaA.increment(10);
|
|
replicaB.increment(7);
|
|
|
|
// Act - merge in both directions
|
|
const mergedA = createGCounterFromState('node-a', replicaA.getState());
|
|
mergedA.applyState(replicaB.getState());
|
|
|
|
const mergedB = createGCounterFromState('node-b', replicaB.getState());
|
|
mergedB.applyState(replicaA.getState());
|
|
|
|
// Assert - both converge to 17
|
|
expect(mergedA.get()).toBe(17);
|
|
expect(mergedB.get()).toBe(17);
|
|
});
|
|
|
|
it('should converge three replicas after pairwise merges', () => {
|
|
// Arrange
|
|
const a = createGCounter('node-a');
|
|
const b = createGCounter('node-b');
|
|
const c = createGCounter('node-c');
|
|
a.increment(5);
|
|
b.increment(3);
|
|
c.increment(8);
|
|
|
|
// Act - merge all into each
|
|
a.merge(b);
|
|
a.merge(c);
|
|
b.merge(a); // b now has a's merged state (which includes c)
|
|
c.merge(a);
|
|
|
|
// Assert
|
|
expect(a.get()).toBe(16);
|
|
expect(b.get()).toBe(16);
|
|
expect(c.get()).toBe(16);
|
|
});
|
|
});
|
|
|
|
describe('commutativity', () => {
|
|
it('should produce same result regardless of merge order: merge(a,b) === merge(b,a)', () => {
|
|
// Arrange
|
|
const a = createGCounter('node-a');
|
|
const b = createGCounter('node-b');
|
|
a.increment(4);
|
|
b.increment(9);
|
|
|
|
// Act
|
|
const ab = createGCounterFromState('node-x', a.getState());
|
|
ab.applyState(b.getState());
|
|
|
|
const ba = createGCounterFromState('node-y', b.getState());
|
|
ba.applyState(a.getState());
|
|
|
|
// Assert
|
|
expect(ab.get()).toBe(ba.get());
|
|
expect(ab.get()).toBe(13);
|
|
});
|
|
});
|
|
|
|
describe('idempotency', () => {
|
|
it('should produce same value when merging with self: merge(a,a) === a', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
counter.increment(7);
|
|
const valueBefore = counter.get();
|
|
|
|
// Act
|
|
counter.applyState(counter.getState());
|
|
|
|
// Assert
|
|
expect(counter.get()).toBe(valueBefore);
|
|
expect(counter.get()).toBe(7);
|
|
});
|
|
|
|
it('should produce same value when merging same state multiple times', () => {
|
|
// Arrange
|
|
const a = createGCounter('node-a');
|
|
const b = createGCounter('node-b');
|
|
a.increment(5);
|
|
b.increment(3);
|
|
const stateB = b.getState();
|
|
|
|
// Act - apply same state 3 times
|
|
a.applyState(stateB);
|
|
a.applyState(stateB);
|
|
a.applyState(stateB);
|
|
|
|
// Assert
|
|
expect(a.get()).toBe(8);
|
|
});
|
|
});
|
|
|
|
describe('factory from state', () => {
|
|
it('should restore counter from serialized state', () => {
|
|
// Arrange
|
|
const original = createGCounter('node-a');
|
|
original.increment(12);
|
|
const state = original.getState();
|
|
|
|
// Act
|
|
const restored = createGCounterFromState('node-a', state);
|
|
|
|
// Assert
|
|
expect(restored.get()).toBe(12);
|
|
expect(restored.getLocalCount()).toBe(12);
|
|
});
|
|
});
|
|
|
|
describe('type guards and utilities', () => {
|
|
it('should validate correct GCounterState', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
counter.increment(5);
|
|
const state = counter.getState();
|
|
|
|
// Assert
|
|
expect(isGCounterState(state)).toBe(true);
|
|
});
|
|
|
|
it('should reject invalid state objects', () => {
|
|
expect(isGCounterState(null)).toBe(false);
|
|
expect(isGCounterState({})).toBe(false);
|
|
expect(isGCounterState({ counts: { a: -1 }, version: 0, lastUpdated: 0 })).toBe(false);
|
|
});
|
|
|
|
it('should get node count from state', () => {
|
|
// Arrange
|
|
const counter = createGCounter('node-a');
|
|
counter.increment(8);
|
|
|
|
// Act & Assert
|
|
expect(getNodeCountFromState(counter.getState(), 'node-a')).toBe(8);
|
|
expect(getNodeCountFromState(counter.getState(), 'node-b')).toBe(0);
|
|
});
|
|
|
|
it('should get contributing nodes', () => {
|
|
// Arrange
|
|
const a = createGCounter('node-a');
|
|
const b = createGCounter('node-b');
|
|
a.increment(3);
|
|
b.increment(5);
|
|
a.merge(b);
|
|
|
|
// Act
|
|
const contributors = getContributingNodes(a.getState());
|
|
|
|
// Assert
|
|
expect(contributors).toContain('node-a');
|
|
expect(contributors).toContain('node-b');
|
|
expect(contributors).toHaveLength(2);
|
|
});
|
|
});
|
|
});
|