mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
Redesign the Storage Monitor as a storage-composition breakdown (snippets · datasets · app)
This commit is contained in:
@@ -1,89 +1,67 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import {
|
||||
CRITICAL_THRESHOLD,
|
||||
WARNING_THRESHOLD,
|
||||
humanizeBytes,
|
||||
summarizeStorage,
|
||||
} from './storage-estimate';
|
||||
import { humanizeBytes, jsonByteSize, 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');
|
||||
describe('summarizeStorage (composition)', () => {
|
||||
test('with origin usage: app = usage − snippets − datasets, total = usage', () => {
|
||||
const c = summarizeStorage({ usageBytes: 1000, snippetBytes: 100, datasetBytes: 300 });
|
||||
expect(c.originMeasured).toBe(true);
|
||||
expect(c.totalBytes).toBe(1000);
|
||||
expect(c.segments).toEqual([
|
||||
{ key: 'snippets', label: 'Snippets', bytes: 100 },
|
||||
{ key: 'datasets', label: 'Datasets', bytes: 300 },
|
||||
{ key: 'app', label: 'App', bytes: 600 },
|
||||
]);
|
||||
});
|
||||
|
||||
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('without origin usage: snippets + datasets only, total is their sum', () => {
|
||||
const c = summarizeStorage({ snippetBytes: 100, datasetBytes: 300 });
|
||||
expect(c.originMeasured).toBe(false);
|
||||
expect(c.totalBytes).toBe(400);
|
||||
expect(c.segments.map((s) => s.key)).toEqual(['snippets', 'datasets']);
|
||||
});
|
||||
|
||||
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');
|
||||
test('usage smaller than measured data falls back to snippets+datasets (estimate lag)', () => {
|
||||
const c = summarizeStorage({ usageBytes: 50, snippetBytes: 100, datasetBytes: 300 });
|
||||
expect(c.originMeasured).toBe(false);
|
||||
expect(c.totalBytes).toBe(400);
|
||||
expect(c.segments).toHaveLength(2);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
test('usage exactly equal to measured data yields a zero-byte app segment', () => {
|
||||
const c = summarizeStorage({ usageBytes: 400, snippetBytes: 100, datasetBytes: 300 });
|
||||
expect(c.originMeasured).toBe(true);
|
||||
expect(c.segments.find((s) => s.key === 'app')?.bytes).toBe(0);
|
||||
});
|
||||
|
||||
describe('availability', () => {
|
||||
test('missing usage -> unavailable', () => {
|
||||
const s = summarizeStorage({ quota: 1000 });
|
||||
expect(s.available).toBe(false);
|
||||
expect(s.fraction).toBe(0);
|
||||
});
|
||||
test('invalid / negative / non-finite inputs degrade to 0 bytes', () => {
|
||||
const c = summarizeStorage({ usageBytes: NaN, snippetBytes: -5, datasetBytes: Infinity });
|
||||
expect(c.segments[0].bytes).toBe(0); // snippets
|
||||
expect(c.segments[1].bytes).toBe(0); // datasets
|
||||
expect(c.originMeasured).toBe(false); // usage NaN → unusable
|
||||
expect(c.totalBytes).toBe(0);
|
||||
});
|
||||
|
||||
test('missing quota -> unavailable', () => {
|
||||
const s = summarizeStorage({ usage: 1000 });
|
||||
expect(s.available).toBe(false);
|
||||
expect(s.fraction).toBe(0);
|
||||
});
|
||||
test('empty workspace: every segment is zero, total zero', () => {
|
||||
const c = summarizeStorage({ snippetBytes: 0, datasetBytes: 0 });
|
||||
expect(c.totalBytes).toBe(0);
|
||||
expect(c.segments.every((s) => s.bytes === 0)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
test('both missing (empty input) -> unavailable', () => {
|
||||
const s = summarizeStorage({});
|
||||
expect(s.available).toBe(false);
|
||||
});
|
||||
describe('jsonByteSize', () => {
|
||||
test('measures the UTF-8 byte length of the JSON serialization', () => {
|
||||
expect(jsonByteSize({ a: 1 })).toBe(new TextEncoder().encode('{"a":1}').length);
|
||||
});
|
||||
|
||||
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('counts multi-byte characters by their UTF-8 size', () => {
|
||||
// "é" is 2 UTF-8 bytes; JSON.stringify("é") => the 4-byte string «"é"».
|
||||
expect(jsonByteSize('é')).toBe(4);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
test('an unserializable (circular) value degrades to 0', () => {
|
||||
const a: Record<string, unknown> = {};
|
||||
a.self = a;
|
||||
expect(jsonByteSize(a)).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user