// Pure-Dart tests for the run aggregate. No widgets, no bindings, no channel — // this is the part that has to be correct regardless of what draws it. import 'package:flutter_test/flutter_test.dart'; import 'package:iperf3gui/domain/models/iperf3_event.dart'; import 'package:iperf3gui/domain/models/iperf3_session.dart'; void main() { Iperf3Interval sample(double start, double bits, {int bytes = 1000, int? retransmits, bool omitted = false}) => Iperf3Interval( start: start, end: start + 1, bytes: bytes, bitsPerSecond: bits, retransmits: retransmits, omitted: omitted, ); group('Iperf3Session', () { test('starts empty', () { final session = Iperf3Session(); expect(session.isEmpty, isTrue); expect(session.sampleCount, isZero); expect(session.intervals, isEmpty); expect(session.start, isNull); expect(session.summary, isNull); expect(session.errors, isEmpty); expect(session.stopped, isFalse); expect(session.hasError, isFalse); }); test('statistics are null before any sample', () { final session = Iperf3Session(); expect(session.latestBitsPerSecond, isNull); expect(session.meanBitsPerSecond, isNull); expect(session.peakBitsPerSecond, isNull); expect(session.totalRetransmits, isNull); }); test('records start, intervals and summary', () { final session = Iperf3Session() ..add(const Iperf3Start(targetHost: 'host', targetPort: 5201)) ..add(sample(0, 100)) ..add(const Iperf3End( sent: Iperf3Total(seconds: 1, bytes: 10, bitsPerSecond: 80), )); expect(session.isEmpty, isFalse); expect(session.start!.targetHost, equals('host')); expect(session.sampleCount, equals(1)); expect(session.summary!.sent!.bitsPerSecond, equals(80)); }); test('excludes omitted warm-up intervals from the series', () { final session = Iperf3Session() ..add(sample(0, 10, omitted: true)) ..add(sample(1, 100)) ..add(sample(2, 200)); expect(session.sampleCount, equals(2)); expect(session.meanBitsPerSecond, equals(150), reason: 'the omitted 10 would have pulled the mean to ~103'); }); test('computes mean, peak and latest over the samples', () { final session = Iperf3Session() ..add(sample(0, 100)) ..add(sample(1, 400)) ..add(sample(2, 250)); expect(session.latestBitsPerSecond, equals(250)); expect(session.peakBitsPerSecond, equals(400)); expect(session.meanBitsPerSecond, equals(250)); }); test('sums sampled bytes', () { final session = Iperf3Session() ..add(sample(0, 1, bytes: 1500)) ..add(sample(1, 1, bytes: 2500)); expect(session.sampledBytes, equals(4000)); }); test('sums retransmits when reported', () { final session = Iperf3Session() ..add(sample(0, 1, retransmits: 2)) ..add(sample(1, 1, retransmits: 5)); expect(session.totalRetransmits, equals(7)); }); test('retransmits stay null when never reported, not zero', () { // UDP reports no retransmit counts; "not measured" must not read as 0. final session = Iperf3Session() ..add(sample(0, 1)) ..add(sample(1, 1)); expect(session.totalRetransmits, isNull); }); test('collects errors in order and flags them', () { final session = Iperf3Session() ..add(const Iperf3Error('first')) ..add(const Iperf3Error('second')); expect(session.hasError, isTrue); expect(session.errors, equals(['first', 'second'])); expect(session.isEmpty, isFalse); }); test('records a stop', () { final session = Iperf3Session()..add(const Iperf3Stopped()); expect(session.stopped, isTrue); }); test('ignores log lines', () { final session = Iperf3Session()..add(const Iperf3LogLine('noise')); expect(session.isEmpty, isTrue); }); test('clear returns it to the empty state', () { final session = Iperf3Session() ..add(const Iperf3Start(targetHost: 'host')) ..add(sample(0, 100)) ..add(const Iperf3Error('boom')) ..add(const Iperf3Stopped()) ..add(const Iperf3End()) ..clear(); expect(session.isEmpty, isTrue); expect(session.stopped, isFalse); expect(session.hasError, isFalse); expect(session.peakBitsPerSecond, isNull); }); test('exposed collections are unmodifiable', () { final session = Iperf3Session()..add(sample(0, 1)); expect(() => session.intervals.add(sample(1, 1)), throwsUnsupportedError); expect(() => session.errors.add('x'), throwsUnsupportedError); }); }); }