mirror of
https://github.com/misenhower/splatoon3.ink.git
synced 2026-03-21 17:54:13 -05:00
Add unit tests for fs utilities and ValueCache
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
b0083d08cc
commit
6e4b57f6a7
118
app/common/ValueCache.test.mjs
Normal file
118
app/common/ValueCache.test.mjs
Normal file
|
|
@ -0,0 +1,118 @@
|
||||||
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||||
|
|
||||||
|
vi.mock('fs/promises', () => ({
|
||||||
|
default: {
|
||||||
|
readFile: vi.fn(),
|
||||||
|
writeFile: vi.fn(),
|
||||||
|
mkdir: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const fs = (await import('fs/promises')).default;
|
||||||
|
|
||||||
|
const { default: ValueCache } = await import('./ValueCache.mjs');
|
||||||
|
|
||||||
|
describe('ValueCache', () => {
|
||||||
|
let cache;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cache = new ValueCache('test-key');
|
||||||
|
vi.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('path', () => {
|
||||||
|
it('returns storage/cache/{key}.json', () => {
|
||||||
|
expect(cache.path).toBe('storage/cache/test-key.json');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the key from constructor', () => {
|
||||||
|
const other = new ValueCache('other');
|
||||||
|
expect(other.path).toBe('storage/cache/other.json');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('get', () => {
|
||||||
|
it('returns parsed JSON when valid and not expired', async () => {
|
||||||
|
const item = { data: 'hello', expires: Date.now() + 60000, cachedAt: new Date().toISOString() };
|
||||||
|
fs.readFile.mockResolvedValue(JSON.stringify(item));
|
||||||
|
const result = await cache.get();
|
||||||
|
expect(result).toEqual(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when expired', async () => {
|
||||||
|
const item = { data: 'hello', expires: Date.now() - 60000, cachedAt: new Date().toISOString() };
|
||||||
|
fs.readFile.mockResolvedValue(JSON.stringify(item));
|
||||||
|
expect(await cache.get()).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns item when no expiry is set', async () => {
|
||||||
|
const item = { data: 'hello', expires: null, cachedAt: new Date().toISOString() };
|
||||||
|
fs.readFile.mockResolvedValue(JSON.stringify(item));
|
||||||
|
const result = await cache.get();
|
||||||
|
expect(result).toEqual(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when file is missing', async () => {
|
||||||
|
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
||||||
|
expect(await cache.get()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getData', () => {
|
||||||
|
it('returns .data field from cached item', async () => {
|
||||||
|
const item = { data: { foo: 'bar' }, expires: Date.now() + 60000, cachedAt: new Date().toISOString() };
|
||||||
|
fs.readFile.mockResolvedValue(JSON.stringify(item));
|
||||||
|
expect(await cache.getData()).toEqual({ foo: 'bar' });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no cache', async () => {
|
||||||
|
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
||||||
|
expect(await cache.getData()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCachedAt', () => {
|
||||||
|
it('returns Date from cachedAt field', async () => {
|
||||||
|
const cachedAt = '2024-06-15T10:00:00.000Z';
|
||||||
|
const item = { data: 'x', cachedAt };
|
||||||
|
fs.readFile.mockResolvedValue(JSON.stringify(item));
|
||||||
|
const result = await cache.getCachedAt();
|
||||||
|
expect(result).toEqual(new Date(cachedAt));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns null when no data', async () => {
|
||||||
|
fs.readFile.mockRejectedValue(new Error('ENOENT'));
|
||||||
|
expect(await cache.getCachedAt()).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('setData', () => {
|
||||||
|
it('writes JSON with data, expires, and cachedAt', async () => {
|
||||||
|
fs.mkdir.mockResolvedValue(undefined);
|
||||||
|
fs.writeFile.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await cache.setData({ key: 'value' }, Date.now() + 60000);
|
||||||
|
|
||||||
|
expect(fs.mkdir).toHaveBeenCalledWith('storage/cache', { recursive: true });
|
||||||
|
expect(fs.writeFile).toHaveBeenCalledWith(
|
||||||
|
'storage/cache/test-key.json',
|
||||||
|
expect.stringContaining('"key": "value"'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const written = JSON.parse(fs.writeFile.mock.calls[0][1]);
|
||||||
|
expect(written.data).toEqual({ key: 'value' });
|
||||||
|
expect(written.expires).toBeDefined();
|
||||||
|
expect(written.cachedAt).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes null expires when not specified', async () => {
|
||||||
|
fs.mkdir.mockResolvedValue(undefined);
|
||||||
|
fs.writeFile.mockResolvedValue(undefined);
|
||||||
|
|
||||||
|
await cache.setData('test');
|
||||||
|
|
||||||
|
const written = JSON.parse(fs.writeFile.mock.calls[0][1]);
|
||||||
|
expect(written.expires).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
49
app/common/fs.test.mjs
Normal file
49
app/common/fs.test.mjs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
import { describe, it, expect, vi } from 'vitest';
|
||||||
|
import { mkdirp, exists, olderThan } from './fs.mjs';
|
||||||
|
|
||||||
|
vi.mock('fs/promises', () => ({
|
||||||
|
default: {
|
||||||
|
mkdir: vi.fn(),
|
||||||
|
access: vi.fn(),
|
||||||
|
stat: vi.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const fs = (await import('fs/promises')).default;
|
||||||
|
|
||||||
|
describe('mkdirp', () => {
|
||||||
|
it('calls fs.mkdir with recursive: true', async () => {
|
||||||
|
fs.mkdir.mockResolvedValue(undefined);
|
||||||
|
await mkdirp('/some/path');
|
||||||
|
expect(fs.mkdir).toHaveBeenCalledWith('/some/path', { recursive: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('exists', () => {
|
||||||
|
it('returns true when fs.access succeeds', async () => {
|
||||||
|
fs.access.mockResolvedValue(undefined);
|
||||||
|
expect(await exists('/some/file')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when fs.access throws', async () => {
|
||||||
|
fs.access.mockRejectedValue(new Error('ENOENT'));
|
||||||
|
expect(await exists('/missing/file')).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('olderThan', () => {
|
||||||
|
it('returns true when mtime is before cutoff', async () => {
|
||||||
|
fs.stat.mockResolvedValue({ mtime: new Date('2024-01-01') });
|
||||||
|
expect(await olderThan('/file', new Date('2024-06-01'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns false when mtime is after cutoff', async () => {
|
||||||
|
fs.stat.mockResolvedValue({ mtime: new Date('2024-06-01') });
|
||||||
|
expect(await olderThan('/file', new Date('2024-01-01'))).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns true when file is missing', async () => {
|
||||||
|
fs.stat.mockRejectedValue(new Error('ENOENT'));
|
||||||
|
expect(await olderThan('/missing', new Date())).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user