Shumiland/tests/helpers/memory.test.js
Vadym Samoilenko 9b41fa447a
Some checks are pending
CI / Type Check (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Unit Tests (push) Waiting to run
Deploy / Build & Push Image (push) Waiting to run
Deploy / Deploy to VPS (push) Blocked by required conditions
feat: complete backend B1-B7 — Payload CMS, ezy payments, leads, deploy
- B1: Next.js 15 + Payload CMS 3.0 + Postgres 16, ESLint, Prettier, Husky, Vitest
- B2: 9 collections, 6 globals, 12 Page Builder blocks, access control, slugify/revalidate hooks
- B3: ezy.com.ua payments, Binotel HMAC webhook, leads API, Telegram bot, Resend email, rate limiting
- B4: Tariffs collection with ezy API sync (cron + manual), dynamic pricing source-of-truth
- B5: 13 test files covering unit libs and all API routes
- B6: Dockerfile multi-stage, docker-compose.prod.yml, nginx.conf SSL, GitHub Actions CI/CD, health endpoint
- B7: docs/admin-guide-ua.md (marketer guide), docs/deploy.md (VPS instructions), README quickstart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 19:14:54 +01:00

219 lines
7.8 KiB
JavaScript

/**
* 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())
})
})
})