feat: Telegram fleet alerts, forge sigil scramble, UI polish, agent ops
- Calibrate: per-event Telegram/SMTP toggles, test notification, chat ID help - Notify on agent connect/reconnect, offline/hashrate/rejection, forge complete - Sigil scramble post-forge uniquification and Dispense Reveal ceremony - Full system check, desktop push, BITS/host-binary persistence, Path Tracer - Dashboard/Crucible visual polish, haptics, sacred geometry, mobile nav - README documents alerts, sigil scramble, and pack-usb workflow - USB bundle repacked via pack-usb.bat (AetherForge.exe + synced agent source)
This commit is contained in:
63
server/web/src/help/chartSampleData.test.ts
Normal file
63
server/web/src/help/chartSampleData.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
generateSampleSeries,
|
||||
validateChartSeries,
|
||||
resolveChartSeries,
|
||||
chartSeriesDelta,
|
||||
chartSeriesPeak,
|
||||
SAMPLE_CONTRIBUTION_BARS,
|
||||
SAMPLE_FLEET_PREVIEW,
|
||||
} from './chartSampleData';
|
||||
|
||||
describe('chartSampleData', () => {
|
||||
const kinds = ['hashrate', 'accept', 'cpu', 'mem', 'gpu'] as const;
|
||||
|
||||
it.each(kinds)('generateSampleSeries(%s) validates', (kind) => {
|
||||
const series = generateSampleSeries(kind, 48);
|
||||
expect(series).toHaveLength(48);
|
||||
const v = validateChartSeries(series);
|
||||
expect(v.ok, v.errors.join('; ')).toBe(true);
|
||||
expect(chartSeriesPeak(series)).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('hashrate sample trends upward (mining ramp)', () => {
|
||||
const series = generateSampleSeries('hashrate', 48);
|
||||
expect(series[series.length - 1].value).toBeGreaterThan(series[0].value);
|
||||
const delta = chartSeriesDelta(series);
|
||||
expect(delta).not.toBeNull();
|
||||
expect(delta!).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('accept sample stays in realistic pool band', () => {
|
||||
const series = generateSampleSeries('accept', 48);
|
||||
for (const p of series) {
|
||||
expect(p.value).toBeGreaterThanOrEqual(90);
|
||||
expect(p.value).toBeLessThanOrEqual(100);
|
||||
}
|
||||
});
|
||||
|
||||
it('resolveChartSeries uses sample when live is empty', () => {
|
||||
const { data, mode } = resolveChartSeries([], 'hashrate');
|
||||
expect(mode).toBe('sample');
|
||||
expect(data.length).toBe(48);
|
||||
expect(validateChartSeries(data).ok).toBe(true);
|
||||
});
|
||||
|
||||
it('resolveChartSeries prefers live when enough points', () => {
|
||||
const live = generateSampleSeries('cpu', 20).map((p, i) => ({
|
||||
...p,
|
||||
value: 40 + i * 0.5,
|
||||
}));
|
||||
const { data, mode } = resolveChartSeries(live, 'cpu');
|
||||
expect(mode).toBe('live');
|
||||
expect(data.length).toBe(20);
|
||||
});
|
||||
|
||||
it('preview constants are internally consistent', () => {
|
||||
const totalPct = SAMPLE_CONTRIBUTION_BARS.reduce((s, b) => s + b.pct, 0);
|
||||
expect(totalPct).toBeGreaterThan(98);
|
||||
expect(totalPct).toBeLessThan(102);
|
||||
expect(SAMPLE_FLEET_PREVIEW.hashrate).toBeGreaterThan(50_000);
|
||||
expect(SAMPLE_FLEET_PREVIEW.xmrPerDay).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user