mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Add storage-estimate core and adapter (M6 Storage Monitor groundwork)
Portable summarizer (clamped fraction, ok/warning/critical level, availability flag, humanizeBytes) with the browser StorageManager.estimate() feature-detect isolated to the infrastructure adapter. No UI consumer yet — core-first per the AI developer protocol; the Storage Monitor panel (spec §02) wires to it next.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import {
|
||||
CRITICAL_THRESHOLD,
|
||||
WARNING_THRESHOLD,
|
||||
humanizeBytes,
|
||||
summarizeStorage,
|
||||
} from './storage-estimate';
|
||||
|
||||
describe('summarizeStorage', () => {
|
||||
test('computes the fraction and passes usage/quota through', () => {
|
||||
const s = summarizeStorage({ usage: 250, quota: 1000 });
|
||||
expect(s.available).toBe(true);
|
||||
expect(s.usedBytes).toBe(250);
|
||||
expect(s.quotaBytes).toBe(1000);
|
||||
expect(s.fraction).toBeCloseTo(0.25);
|
||||
expect(s.level).toBe('ok');
|
||||
});
|
||||
|
||||
test('clamps fraction to 1 when usage exceeds quota', () => {
|
||||
const s = summarizeStorage({ usage: 1500, quota: 1000 });
|
||||
expect(s.fraction).toBe(1);
|
||||
expect(s.level).toBe('critical');
|
||||
});
|
||||
|
||||
test('guards divide-by-zero: quota of 0 is unusable', () => {
|
||||
const s = summarizeStorage({ usage: 10, quota: 0 });
|
||||
expect(s.available).toBe(false);
|
||||
expect(s.fraction).toBe(0);
|
||||
expect(s.usedBytes).toBe(0);
|
||||
expect(s.quotaBytes).toBe(0);
|
||||
expect(s.level).toBe('ok');
|
||||
});
|
||||
|
||||
describe('level boundaries', () => {
|
||||
test('just below warning (0.79) is ok', () => {
|
||||
expect(summarizeStorage({ usage: 79, quota: 100 }).level).toBe('ok');
|
||||
});
|
||||
|
||||
test('exactly at warning (0.80) is warning', () => {
|
||||
const s = summarizeStorage({ usage: 80, quota: 100 });
|
||||
expect(s.fraction).toBeCloseTo(WARNING_THRESHOLD);
|
||||
expect(s.level).toBe('warning');
|
||||
});
|
||||
|
||||
test('just below critical (0.94) is warning', () => {
|
||||
expect(summarizeStorage({ usage: 94, quota: 100 }).level).toBe('warning');
|
||||
});
|
||||
|
||||
test('exactly at critical (0.95) is critical', () => {
|
||||
const s = summarizeStorage({ usage: 95, quota: 100 });
|
||||
expect(s.fraction).toBeCloseTo(CRITICAL_THRESHOLD);
|
||||
expect(s.level).toBe('critical');
|
||||
});
|
||||
});
|
||||
|
||||
describe('availability', () => {
|
||||
test('missing usage -> unavailable', () => {
|
||||
const s = summarizeStorage({ quota: 1000 });
|
||||
expect(s.available).toBe(false);
|
||||
expect(s.fraction).toBe(0);
|
||||
});
|
||||
|
||||
test('missing quota -> unavailable', () => {
|
||||
const s = summarizeStorage({ usage: 1000 });
|
||||
expect(s.available).toBe(false);
|
||||
expect(s.fraction).toBe(0);
|
||||
});
|
||||
|
||||
test('both missing (empty input) -> unavailable', () => {
|
||||
const s = summarizeStorage({});
|
||||
expect(s.available).toBe(false);
|
||||
});
|
||||
|
||||
test('non-finite or negative values -> unavailable', () => {
|
||||
expect(summarizeStorage({ usage: NaN, quota: 1000 }).available).toBe(false);
|
||||
expect(summarizeStorage({ usage: Infinity, quota: 1000 }).available).toBe(false);
|
||||
expect(summarizeStorage({ usage: -1, quota: 1000 }).available).toBe(false);
|
||||
expect(summarizeStorage({ usage: 10, quota: -5 }).available).toBe(false);
|
||||
});
|
||||
|
||||
test('zero usage with a real quota is available and ok', () => {
|
||||
const s = summarizeStorage({ usage: 0, quota: 1000 });
|
||||
expect(s.available).toBe(true);
|
||||
expect(s.fraction).toBe(0);
|
||||
expect(s.level).toBe('ok');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('humanizeBytes', () => {
|
||||
test('zero renders as whole bytes', () => {
|
||||
expect(humanizeBytes(0)).toBe('0 B');
|
||||
});
|
||||
|
||||
test('small values stay in whole bytes', () => {
|
||||
expect(humanizeBytes(1)).toBe('1 B');
|
||||
expect(humanizeBytes(512)).toBe('512 B');
|
||||
expect(humanizeBytes(1023)).toBe('1023 B');
|
||||
});
|
||||
|
||||
test('KB boundary (1024) and within KB', () => {
|
||||
expect(humanizeBytes(1024)).toBe('1.0 KB');
|
||||
expect(humanizeBytes(1536)).toBe('1.5 KB');
|
||||
});
|
||||
|
||||
test('MB boundary and within MB', () => {
|
||||
expect(humanizeBytes(1024 * 1024)).toBe('1.0 MB');
|
||||
expect(humanizeBytes(5 * 1024 * 1024)).toBe('5.0 MB');
|
||||
expect(humanizeBytes(2.5 * 1024 * 1024)).toBe('2.5 MB');
|
||||
});
|
||||
|
||||
test('GB boundary', () => {
|
||||
expect(humanizeBytes(1024 * 1024 * 1024)).toBe('1.0 GB');
|
||||
});
|
||||
|
||||
test('caps at the largest unit (TB)', () => {
|
||||
expect(humanizeBytes(1024 ** 4)).toBe('1.0 TB');
|
||||
expect(humanizeBytes(1024 ** 5)).toBe('1024.0 TB');
|
||||
});
|
||||
|
||||
test('negative and non-finite inputs degrade to 0 B', () => {
|
||||
expect(humanizeBytes(-1)).toBe('0 B');
|
||||
expect(humanizeBytes(NaN)).toBe('0 B');
|
||||
expect(humanizeBytes(Infinity)).toBe('0 B');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user