/** * Tests for .claude/helpers/memory.js * * Run: node --test tests/helpers/memory.test.js * * Coverage gaps addressed: * - loadMemory: missing file, corrupted JSON, valid file * - saveMemory: directory creation, JSON format * - commands.get: with key, without key, missing key * - commands.set: missing key arg, _updated timestamp, overwrite * - commands.delete: missing key arg, non-existent key * - commands.keys: filters _-prefixed meta keys * - commands.clear: resets to empty object * - Error handling: corrupted JSON masked silently */ 'use strict' const { describe, it, before, after, beforeEach } = require('node:test') const assert = require('node:assert/strict') const fs = require('node:fs') const path = require('node:path') const os = require('node:os') // Helper to load the module with a custom CWD so MEMORY_FILE points to a tmp dir function loadModule(cwd) { // Clear require cache so MEMORY_DIR/MEMORY_FILE are re-evaluated each time const modPath = require.resolve('../../.claude/helpers/memory.js') delete require.cache[modPath] const origCwd = process.cwd process.cwd = () => cwd try { const mod = require('../../.claude/helpers/memory.js') return mod } finally { process.cwd = origCwd delete require.cache[modPath] } } describe('memory.js', () => { let tmpDir let commands let memoryFile before(() => { tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mem-test-')) memoryFile = path.join(tmpDir, '.claude-flow', 'data', 'memory.json') }) after(() => { fs.rmSync(tmpDir, { recursive: true, force: true }) }) beforeEach(() => { // Reset memory file before each test const dir = path.dirname(memoryFile) if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true }) commands = loadModule(tmpDir) }) // ── loadMemory ───────────────────────────────────────────────────────────── describe('loadMemory (via commands.get)', () => { it('returns {} when memory file does not exist', () => { const result = commands.get(undefined) assert.deepEqual(result, {}) }) it('returns {} and does not throw when file contains corrupted JSON', () => { const dir = path.dirname(memoryFile) fs.mkdirSync(dir, { recursive: true }) fs.writeFileSync(memoryFile, '{ not valid json !!!') // Should not throw; silently returns {} const result = commands.get(undefined) assert.deepEqual(result, {}) }) it('parses and returns stored memory when file is valid JSON', () => { const dir = path.dirname(memoryFile) fs.mkdirSync(dir, { recursive: true }) fs.writeFileSync(memoryFile, JSON.stringify({ foo: 'bar' }, null, 2)) const result = commands.get('foo') assert.equal(result, 'bar') }) // EDGE CASE: empty file (0 bytes) should not crash it('returns {} when file is empty', () => { const dir = path.dirname(memoryFile) fs.mkdirSync(dir, { recursive: true }) fs.writeFileSync(memoryFile, '') const result = commands.get(undefined) assert.deepEqual(result, {}) }) }) // ── saveMemory (via commands.set) ────────────────────────────────────────── describe('saveMemory (via commands.set)', () => { it('creates the data directory if it does not exist', () => { commands.set('k', 'v') assert.ok(fs.existsSync(path.dirname(memoryFile))) }) it('writes pretty-printed JSON to disk', () => { commands.set('hello', 'world') const raw = fs.readFileSync(memoryFile, 'utf-8') const parsed = JSON.parse(raw) assert.equal(parsed.hello, 'world') // Pretty-printed → contains newlines assert.ok(raw.includes('\n')) }) }) // ── commands.get ─────────────────────────────────────────────────────────── describe('commands.get', () => { it('returns the whole memory object when no key is provided', () => { commands.set('a', 1) commands.set('b', 2) const result = commands.get(undefined) assert.equal(result.a, 1) assert.equal(result.b, 2) }) it('returns undefined for a key that was never set', () => { const result = commands.get('nonexistent') assert.equal(result, undefined) }) it('returns the value for an existing key', () => { commands.set('mykey', 'myvalue') const result = commands.get('mykey') assert.equal(result, 'myvalue') }) }) // ── commands.set ─────────────────────────────────────────────────────────── describe('commands.set', () => { it('returns undefined and does not write when key is missing', () => { const result = commands.set(undefined, 'value') assert.equal(result, undefined) assert.ok(!fs.existsSync(memoryFile)) }) it('writes a _updated ISO timestamp alongside the value', () => { const before = Date.now() commands.set('ts-test', 'v') const after = Date.now() const raw = JSON.parse(fs.readFileSync(memoryFile, 'utf-8')) const ts = new Date(raw._updated).getTime() assert.ok(ts >= before && ts <= after + 100) }) it('overwrites an existing key without losing other keys', () => { commands.set('x', 'first') commands.set('y', 'other') commands.set('x', 'second') assert.equal(commands.get('x'), 'second') assert.equal(commands.get('y'), 'other') }) }) // ── commands.delete ──────────────────────────────────────────────────────── describe('commands.delete', () => { it('logs error and does not write when key is missing', () => { const result = commands.delete(undefined) assert.equal(result, undefined) assert.ok(!fs.existsSync(memoryFile)) }) it('removes an existing key from memory', () => { commands.set('to-remove', 'val') commands.delete('to-remove') assert.equal(commands.get('to-remove'), undefined) }) it('is a no-op and does not throw when deleting a non-existent key', () => { commands.set('keep', 'val') assert.doesNotThrow(() => commands.delete('ghost')) assert.equal(commands.get('keep'), 'val') }) }) // ── commands.keys ────────────────────────────────────────────────────────── describe('commands.keys', () => { it('returns only user-defined keys, excluding _-prefixed meta keys', () => { commands.set('alpha', 1) commands.set('beta', 2) const keys = commands.keys() assert.ok(keys.includes('alpha')) assert.ok(keys.includes('beta')) assert.ok(!keys.includes('_updated'), '_updated must be filtered out') }) it('returns an empty array when memory is empty', () => { const keys = commands.keys() assert.deepEqual(keys, []) }) }) // ── commands.clear ───────────────────────────────────────────────────────── describe('commands.clear', () => { it('replaces all memory with an empty object', () => { commands.set('a', 1) commands.set('b', 2) commands.clear() assert.deepEqual(commands.get(undefined), {}) }) it('does not throw when memory is already empty', () => { assert.doesNotThrow(() => commands.clear()) }) }) })