import { test } from 'node:test' import assert from 'node:assert/strict' import { STORAGE_KEY, readItems, addItem } from '../src/items.mjs' function memory(initial = null) { let value = initial return { getItem(key) { assert.equal(key, STORAGE_KEY); return value }, setItem(key, next) { assert.equal(key, STORAGE_KEY); value = next }, } } test('empty/new/reloaded local items persist exactly', () => { const storage = memory() assert.deepEqual(readItems(storage), []) const first = addItem(storage) assert.deepEqual(readItems(storage), first) assert.equal(addItem(storage)[1].id, 2) }) test('invalid data is preserved, never reset by read or add', () => { for (const raw of ['broken', '{}', '[null]', '[{"id":1}]', JSON.stringify([{ id: 1, name: 'a', detail: '' }, { id: 1, name: 'b', detail: '' }])]) { const storage = memory(raw) assert.throws(() => readItems(storage)) assert.throws(() => addItem(storage)) assert.equal(storage.getItem(STORAGE_KEY), raw) } }) test('unavailable storage and quota failures propagate without claiming success', () => { assert.throws(() => readItems({ getItem() { throw Error('blocked') } }), /blocked/) const storage = memory() storage.setItem = () => { throw Error('quota') } assert.throws(() => addItem(storage), /quota/) assert.deepEqual(readItems(storage), []) }) test('existing IDs retained and next ID follows maximum, not length', () => { const storage = memory(JSON.stringify([{ id: 5, name: 'Existing', detail: 'Preserve' }])) const items = addItem(storage) assert.deepEqual(items[0], { id: 5, name: 'Existing', detail: 'Preserve' }) assert.equal(items[1].id, 6) }) test('size and item count bounds preserve original data', () => { const huge = ' '.repeat(1024 * 1024 + 1) assert.throws(() => readItems(memory(huge)), /size/) const raw = JSON.stringify(Array.from({ length: 1000 }, (_, i) => ({ id: i + 1, name: 'Saved', detail: '' }))) const storage = memory(raw) assert.throws(() => addItem(storage), /1000/) assert.equal(storage.getItem(STORAGE_KEY), raw) }) test('unsafe identifiers, extra properties and invalid names preserve storage', () => { for (const item of [ { id: 0, name: 'a', detail: '' }, { id: 1.5, name: 'a', detail: '' }, { id: Number.MAX_SAFE_INTEGER + 1, name: 'a', detail: '' }, { id: 1, name: '', detail: '' }, { id: 1, name: 'a'.repeat(201), detail: '' }, { id: 1, name: 'a', detail: 'b'.repeat(501) }, { id: 1, name: 'a', detail: '', extra: true }, ]) { const raw = JSON.stringify([item]) const storage = memory(raw) assert.throws(() => addItem(storage)) assert.equal(storage.getItem(STORAGE_KEY), raw) } }) test('exhausted safe integer ID refuses a new item without changing original', () => { const raw = JSON.stringify([{ id: Number.MAX_SAFE_INTEGER, name: 'Last', detail: '' }]) const storage = memory(raw) assert.throws(() => addItem(storage), /exhausted/) assert.equal(storage.getItem(STORAGE_KEY), raw) }) test('an append crossing the serialized-size boundary preserves readable data', () => { const items = Array.from({ length: 999 }, (_, i) => ({ id: i + 1, name: 'x'.repeat(200), detail: 'x'.repeat(500) })) const limit = 1024 * 1024 let extra = Math.floor((limit - JSON.stringify(items).length) / 5) for (const item of items) { const escaped = Math.min(extra, 200) item.name = '\u0000'.repeat(escaped) + 'x'.repeat(200 - escaped) extra -= escaped } const raw = JSON.stringify(items) assert.ok(raw.length <= limit && raw.length > limit - 5) const storage = memory(raw) assert.equal(readItems(storage).length, 999) assert.throws(() => addItem(storage), /size/) assert.equal(storage.getItem(STORAGE_KEY), raw) assert.equal(readItems(storage).length, 999) }) test('quota refusal during append preserves populated storage byte for byte', () => { const raw = JSON.stringify([{ id: 7, name: 'Keep', detail: 'Existing data' }]) const storage = memory(raw) storage.setItem = () => { throw Error('quota') } assert.throws(() => addItem(storage), /quota/) assert.equal(storage.getItem(STORAGE_KEY), raw) })