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>
725 lines
22 KiB
TypeScript
725 lines
22 KiB
TypeScript
/**
|
|
* JSON Reader Unit Tests
|
|
*
|
|
* Tests for reading data from JSON files:
|
|
* - Initialization with file path validation
|
|
* - Reading all records from various JSON structures
|
|
* - Incremental reading by file modification time
|
|
* - Record count operations
|
|
* - Data extraction for different sources (Claude Flow, intelligence, daemon)
|
|
* - Data transformation for cloud sync
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
|
|
// Mock fs module
|
|
vi.mock('fs');
|
|
|
|
// Mock secure-json-parse
|
|
vi.mock('secure-json-parse', () => ({
|
|
default: {
|
|
parse: vi.fn((content: string) => JSON.parse(content)),
|
|
},
|
|
}));
|
|
|
|
import {
|
|
JSONReader,
|
|
createJSONReader,
|
|
type JSONReaderConfig,
|
|
type JSONRecord,
|
|
} from '../../../../src/sync/readers/json-reader.js';
|
|
import type { SyncSource } from '../../../../src/sync/interfaces.js';
|
|
import secureJsonParse from 'secure-json-parse';
|
|
|
|
describe('JSONReader', () => {
|
|
const mockFs = fs as unknown as {
|
|
existsSync: ReturnType<typeof vi.fn>;
|
|
readFileSync: ReturnType<typeof vi.fn>;
|
|
statSync: ReturnType<typeof vi.fn>;
|
|
};
|
|
|
|
const defaultSource: SyncSource = {
|
|
name: 'test-json',
|
|
type: 'json',
|
|
path: 'test.json',
|
|
targetTable: 'aqe.test_table',
|
|
priority: 'high',
|
|
mode: 'full',
|
|
enabled: true,
|
|
};
|
|
|
|
const defaultConfig: JSONReaderConfig = {
|
|
source: defaultSource,
|
|
baseDir: '/test/base',
|
|
environment: 'test-env',
|
|
};
|
|
|
|
let reader: JSONReader;
|
|
let consoleLogSpy: ReturnType<typeof vi.spyOn>;
|
|
let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
|
|
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
|
|
mockFs.existsSync = vi.fn().mockReturnValue(true);
|
|
mockFs.readFileSync = vi.fn().mockReturnValue('{}');
|
|
mockFs.statSync = vi.fn().mockReturnValue({
|
|
mtime: new Date(),
|
|
size: 1024,
|
|
});
|
|
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockImplementation(
|
|
(content: string) => JSON.parse(content)
|
|
);
|
|
|
|
consoleLogSpy = vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
});
|
|
|
|
afterEach(() => {
|
|
consoleLogSpy.mockRestore();
|
|
consoleWarnSpy.mockRestore();
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
|
|
describe('constructor', () => {
|
|
it('should create reader with config', () => {
|
|
reader = new JSONReader(defaultConfig);
|
|
|
|
expect(reader.name).toBe('test-json');
|
|
expect(reader.type).toBe('json');
|
|
});
|
|
|
|
it('should resolve path relative to baseDir', () => {
|
|
reader = new JSONReader(defaultConfig);
|
|
|
|
const expectedPath = path.resolve('/test/base', 'test.json');
|
|
const info = reader.getInfo();
|
|
|
|
expect(info.path).toBe(expectedPath);
|
|
});
|
|
});
|
|
|
|
describe('initialize', () => {
|
|
it('should store file modification time', async () => {
|
|
const modTime = new Date('2024-01-15');
|
|
mockFs.statSync = vi.fn().mockReturnValue({
|
|
mtime: modTime,
|
|
size: 1024,
|
|
});
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const info = reader.getInfo();
|
|
expect(info.modTime).toEqual(modTime);
|
|
});
|
|
|
|
it('should handle missing file gracefully', async () => {
|
|
mockFs.existsSync = vi.fn().mockReturnValue(false);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('File not found')
|
|
);
|
|
});
|
|
|
|
it('should set empty data for missing file', async () => {
|
|
mockFs.existsSync = vi.fn().mockReturnValue(false);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
expect(records).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('readAll', () => {
|
|
beforeEach(async () => {
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
});
|
|
|
|
it('should return empty array if file not found', async () => {
|
|
mockFs.existsSync = vi.fn().mockReturnValue(false);
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
|
|
it('should parse and return JSON data', async () => {
|
|
const data = [{ id: '1', name: 'Test' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(1);
|
|
expect(records[0].id).toBe('1');
|
|
});
|
|
|
|
it('should add source_env to transformed records', async () => {
|
|
const data = [{ id: '1' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records[0]).toHaveProperty('source_env', 'test-env');
|
|
});
|
|
|
|
it('should add created_at if missing', async () => {
|
|
const data = [{ id: '1' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records[0]).toHaveProperty('created_at');
|
|
expect(typeof records[0].created_at).toBe('string');
|
|
});
|
|
|
|
it('should handle parse errors gracefully', async () => {
|
|
mockFs.readFileSync = vi.fn().mockReturnValue('invalid json');
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockImplementation(() => {
|
|
throw new Error('Parse error');
|
|
});
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records).toEqual([]);
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should convert object to key-value array', async () => {
|
|
const data = {
|
|
key1: { value: 'v1' },
|
|
key2: { value: 'v2' },
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(2);
|
|
expect(records[0]).toHaveProperty('key', 'key1');
|
|
expect(records[0]).toHaveProperty('value');
|
|
});
|
|
});
|
|
|
|
describe('readChanged', () => {
|
|
beforeEach(async () => {
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
});
|
|
|
|
it('should return empty array if file not found', async () => {
|
|
mockFs.existsSync = vi.fn().mockReturnValue(false);
|
|
|
|
const records = await reader.readChanged(new Date());
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
|
|
it('should return empty array if file not modified since date', async () => {
|
|
const oldModTime = new Date('2024-01-01');
|
|
mockFs.statSync = vi.fn().mockReturnValue({
|
|
mtime: oldModTime,
|
|
size: 1024,
|
|
});
|
|
|
|
const since = new Date('2024-01-15');
|
|
const records = await reader.readChanged(since);
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
|
|
it('should return all records if file modified after since date', async () => {
|
|
const newModTime = new Date('2024-01-20');
|
|
mockFs.statSync = vi.fn().mockReturnValue({
|
|
mtime: newModTime,
|
|
size: 1024,
|
|
});
|
|
|
|
const data = [{ id: '1' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
const since = new Date('2024-01-15');
|
|
const records = await reader.readChanged(since);
|
|
|
|
expect(records.length).toBe(1);
|
|
});
|
|
});
|
|
|
|
describe('count', () => {
|
|
it('should return record count', async () => {
|
|
const data = [{ id: '1' }, { id: '2' }, { id: '3' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const count = await reader.count();
|
|
|
|
expect(count).toBe(3);
|
|
});
|
|
});
|
|
|
|
describe('close', () => {
|
|
it('should clear internal data', async () => {
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
await reader.close();
|
|
|
|
// After close, internal state should be cleared
|
|
expect(consoleLogSpy).toHaveBeenCalledWith(
|
|
expect.stringContaining('Closed')
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('getInfo', () => {
|
|
it('should return file info', async () => {
|
|
const modTime = new Date('2024-01-15');
|
|
mockFs.statSync = vi.fn().mockReturnValue({
|
|
mtime: modTime,
|
|
size: 2048,
|
|
});
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const info = reader.getInfo();
|
|
|
|
expect(info.path).toBeDefined();
|
|
expect(info.exists).toBe(true);
|
|
expect(info.modTime).toEqual(modTime);
|
|
expect(info.size).toBe(2048);
|
|
});
|
|
|
|
it('should handle missing file', () => {
|
|
mockFs.existsSync = vi.fn().mockReturnValue(false);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
const info = reader.getInfo();
|
|
|
|
expect(info.exists).toBe(false);
|
|
expect(info.modTime).toBeNull();
|
|
expect(info.size).toBe(0);
|
|
});
|
|
});
|
|
|
|
describe('JSON path extraction', () => {
|
|
it('should extract records by JSON path', async () => {
|
|
const configWithPath: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
jsonPath: '$.data.items',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
data: {
|
|
items: [{ id: '1' }, { id: '2' }],
|
|
},
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configWithPath);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(2);
|
|
});
|
|
|
|
it('should handle nested JSON path', async () => {
|
|
const configWithPath: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
jsonPath: '$.level1.level2.level3',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
level1: {
|
|
level2: {
|
|
level3: [{ id: '1' }],
|
|
},
|
|
},
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configWithPath);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(1);
|
|
});
|
|
|
|
it('should return empty array for non-existent path', async () => {
|
|
const configWithPath: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
jsonPath: '$.nonexistent.path',
|
|
},
|
|
};
|
|
|
|
const data = { other: 'data' };
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configWithPath);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
|
|
it('should convert object to records for q-values path', async () => {
|
|
const configWithPath: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
jsonPath: '$.qvalues',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
qvalues: {
|
|
state1: { value: 0.5, visits: 10 },
|
|
state2: { value: 0.8, visits: 20 },
|
|
},
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configWithPath);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(2);
|
|
expect(records[0]).toHaveProperty('state', 'state1');
|
|
});
|
|
});
|
|
|
|
describe('Claude Flow memory extraction', () => {
|
|
it('should extract records from Claude Flow structure', async () => {
|
|
const configClaudeFlow: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'claude-flow-memory',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
'adr-123': { analysis: 'test' },
|
|
'agent-task-1': { status: 'complete' },
|
|
'pattern-auth': { type: 'authentication' },
|
|
'metric-coverage': { value: 0.85 },
|
|
'_metadata': { version: '1.0' },
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configClaudeFlow);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
// Should skip _metadata
|
|
expect(records.length).toBe(4);
|
|
|
|
// Check categories
|
|
const adrRecord = records.find((r) => r.key === 'adr-123');
|
|
expect(adrRecord?.category).toBe('adr-analysis');
|
|
|
|
const agentRecord = records.find((r) => r.key === 'agent-task-1');
|
|
expect(agentRecord?.category).toBe('agent-patterns');
|
|
|
|
const patternRecord = records.find((r) => r.key === 'pattern-auth');
|
|
expect(patternRecord?.category).toBe('patterns');
|
|
|
|
const metricRecord = records.find((r) => r.key === 'metric-coverage');
|
|
expect(metricRecord?.category).toBe('metrics');
|
|
});
|
|
|
|
it('should handle empty Claude Flow store', async () => {
|
|
const configClaudeFlow: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'claude-flow-memory',
|
|
},
|
|
};
|
|
|
|
mockFs.readFileSync = vi.fn().mockReturnValue('{}');
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue({});
|
|
|
|
reader = new JSONReader(configClaudeFlow);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Intelligence JSON extraction', () => {
|
|
it('should extract Q-values from intelligence file', async () => {
|
|
const configIntel: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'intelligence-qlearning',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
qvalues: {
|
|
state1: {
|
|
action1: { value: 0.5, visits: 10 },
|
|
action2: { value: 0.8, visits: 20 },
|
|
},
|
|
},
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configIntel);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(2);
|
|
expect(records[0]).toHaveProperty('state', 'state1');
|
|
expect(records[0]).toHaveProperty('action');
|
|
expect(records[0]).toHaveProperty('q_value');
|
|
expect(records[0]).toHaveProperty('visits');
|
|
});
|
|
|
|
it('should extract memories with embeddings', async () => {
|
|
const configIntel: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'intelligence-memories',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
memories: [
|
|
{
|
|
id: 'mem1',
|
|
type: 'file_access',
|
|
content: 'test content',
|
|
embedding: [0.1, 0.2, 0.3],
|
|
metadata: { key: 'value' },
|
|
timestamp: Date.now(),
|
|
},
|
|
],
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configIntel);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(1);
|
|
expect(records[0]).toHaveProperty('id', 'mem1');
|
|
expect(records[0]).toHaveProperty('memory_type', 'file_access');
|
|
expect(records[0]).toHaveProperty('embedding');
|
|
});
|
|
|
|
it('should generate id for memories without id', async () => {
|
|
const configIntel: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'intelligence-memories',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
memories: [{ content: 'test', type: 'test_type' }],
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configIntel);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records[0].id).toMatch(/^mem_/);
|
|
});
|
|
});
|
|
|
|
describe('Daemon state extraction', () => {
|
|
it('should extract worker stats from daemon state', async () => {
|
|
const configDaemon: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'daemon-state',
|
|
},
|
|
};
|
|
|
|
const data = {
|
|
workers: {
|
|
audit: {
|
|
runCount: 100,
|
|
successCount: 95,
|
|
failureCount: 5,
|
|
avgDuration: 150,
|
|
lastRun: Date.now(),
|
|
},
|
|
metrics: {
|
|
runs: 50,
|
|
successes: 48,
|
|
failures: 2,
|
|
averageDurationMs: 200,
|
|
},
|
|
},
|
|
};
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configDaemon);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records.length).toBe(2);
|
|
|
|
const auditRecord = records.find((r) => r.worker_type === 'audit');
|
|
expect(auditRecord?.run_count).toBe(100);
|
|
expect(auditRecord?.success_count).toBe(95);
|
|
|
|
const metricsRecord = records.find((r) => r.worker_type === 'metrics');
|
|
expect(metricsRecord?.run_count).toBe(50);
|
|
});
|
|
|
|
it('should handle empty workers object', async () => {
|
|
const configDaemon: JSONReaderConfig = {
|
|
...defaultConfig,
|
|
source: {
|
|
...defaultSource,
|
|
name: 'daemon-state',
|
|
},
|
|
};
|
|
|
|
const data = { workers: {} };
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(configDaemon);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('Timestamp transformation', () => {
|
|
it('should convert numeric timestamps to ISO strings', async () => {
|
|
const timestamp = Date.now();
|
|
const data = [{ id: '1', timestamp: timestamp }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(typeof records[0].timestamp).toBe('string');
|
|
expect(records[0].timestamp).toMatch(/\d{4}-\d{2}-\d{2}T/);
|
|
});
|
|
|
|
it('should convert _at fields to ISO strings', async () => {
|
|
const timestamp = Date.now();
|
|
const data = [{ id: '1', last_used_at: timestamp }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(typeof records[0].last_used_at).toBe('string');
|
|
});
|
|
|
|
it('should convert string date to ISO string', async () => {
|
|
const data = [{ id: '1', timestamp: '2024-01-15' }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(records[0].timestamp).toMatch(/\d{4}-\d{2}-\d{2}T/);
|
|
});
|
|
|
|
it('should handle last_update field', async () => {
|
|
const timestamp = Date.now();
|
|
const data = [{ id: '1', last_update: timestamp }];
|
|
mockFs.readFileSync = vi.fn().mockReturnValue(JSON.stringify(data));
|
|
(secureJsonParse.parse as ReturnType<typeof vi.fn>).mockReturnValue(data);
|
|
|
|
reader = new JSONReader(defaultConfig);
|
|
await reader.initialize();
|
|
|
|
const records = await reader.readAll();
|
|
|
|
expect(typeof records[0].last_update).toBe('string');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('createJSONReader', () => {
|
|
it('should create JSONReader instance', () => {
|
|
const config: JSONReaderConfig = {
|
|
source: {
|
|
name: 'test',
|
|
type: 'json',
|
|
path: 'test.json',
|
|
targetTable: 'test_table',
|
|
priority: 'high',
|
|
mode: 'full',
|
|
},
|
|
baseDir: '/test',
|
|
environment: 'test',
|
|
};
|
|
|
|
const reader = createJSONReader(config);
|
|
|
|
expect(reader).toBeInstanceOf(JSONReader);
|
|
});
|
|
});
|