godcrm/backend/services/__tests__/sync-activity.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

76 lines
2.6 KiB
JavaScript

/**
* Sync-agent activity registry — protects live sync agents from the watchdog
* orphan-reaper (see services/agent-job/watchdog.js orphan branch).
*
* Bug: a sync agent (POST /chat, /run) holds conversations.is_processing=true with
* no agent_jobs row. During one long blocking call (no intermediate saveStepMessage)
* updated_at goes stale and the watchdog clears the lock of a STILL-WORKING agent,
* posting «… перестал отвечать». The registry lets the watchdog tell live from dead.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import {
SYNC_ACTIVITY_MAX_MS,
markSyncActive,
markSyncInactive,
isSyncActive,
activeSyncCount,
} from '../chat/agent-execution-shared/sync-activity.js';
describe('sync-activity registry', () => {
beforeEach(() => {
// Drain any leftover entries between tests (module state is process-global).
markSyncInactive(101);
markSyncInactive(202);
markSyncInactive(303);
vi.useRealTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('a freshly marked conversation is live → watchdog must skip it', () => {
expect(isSyncActive(101)).toBe(false); // not yet started
markSyncActive(101);
expect(isSyncActive(101)).toBe(true);
expect(activeSyncCount()).toBe(1);
});
it('accepts string conversation ids (route params arrive as strings)', () => {
markSyncActive('202');
expect(isSyncActive(202)).toBe(true); // number lookup
expect(isSyncActive('202')).toBe(true); // string lookup
});
it('markSyncInactive clears the mark → watchdog may reap', () => {
markSyncActive(101);
expect(isSyncActive(101)).toBe(true);
markSyncInactive(101);
expect(isSyncActive(101)).toBe(false);
expect(activeSyncCount()).toBe(0);
});
it('a genuinely hung agent past the max-age cap is NOT protected (and is evicted)', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-06-04T00:00:00Z'));
markSyncActive(303);
expect(isSyncActive(303)).toBe(true);
// Just under the cap → still protected.
vi.advanceTimersByTime(SYNC_ACTIVITY_MAX_MS - 1000);
expect(isSyncActive(303)).toBe(true);
// Past the cap → reapable, and the stale entry is removed.
vi.advanceTimersByTime(2000);
expect(isSyncActive(303)).toBe(false);
expect(activeSyncCount()).toBe(0);
});
it('null/undefined ids are ignored safely', () => {
expect(() => markSyncActive(null)).not.toThrow();
expect(() => markSyncInactive(undefined)).not.toThrow();
expect(isSyncActive(null)).toBe(false);
expect(isSyncActive(undefined)).toBe(false);
});
});