Files
apidash/test/providers/terminal_providers_test.dart
Ankit Mahato ba8b9e0c46 refactor tests
2025-09-28 14:02:18 +05:30

231 lines
7.9 KiB
Dart

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/providers/terminal_providers.dart';
import 'package:apidash/terminal/terminal.dart';
import 'package:apidash_core/apidash_core.dart';
void main() {
group('TerminalController', () {
late ProviderContainer container;
late TerminalController controller;
setUp(() {
container = ProviderContainer();
controller = container.read(terminalStateProvider.notifier);
});
tearDown(() {
container.dispose();
});
test('initial state is empty', () {
final state = container.read(terminalStateProvider);
expect(state.entries, isEmpty);
expect(state.index, isEmpty);
});
test('log system entries and clear', () {
controller.logSystem(category: 'ui', message: 'opened');
controller.logSystem(
category: 'provider', message: 'updated', level: TerminalLevel.warn);
final state = container.read(terminalStateProvider);
expect(state.entries.length, 2);
expect(state.entries.first.system?.category, anyOf('ui', 'provider'));
// serialization has timestamps, uppercase level and title-cased source
final text = controller.serializeAll();
expect(text, contains('INFO'));
expect(text, contains('System'));
expect(text.toLowerCase(), contains('opened'));
controller.clear();
expect(container.read(terminalStateProvider).entries, isEmpty);
});
test('JS logs map levels and include args', () {
controller.logJs(level: 'log', args: ['hello']);
controller.logJs(level: 'warn', args: ['warn']);
controller.logJs(level: 'error', args: ['err'], context: 'preRequest');
final state = container.read(terminalStateProvider);
expect(state.entries.length, 3);
final levels = state.entries.map((e) => e.level).toList();
expect(levels.contains(TerminalLevel.info), isTrue);
expect(levels.contains(TerminalLevel.warn), isTrue);
expect(levels.contains(TerminalLevel.error), isTrue);
final title0 = controller.titleFor(state.entries.first);
final sub0 = controller.subtitleFor(state.entries.first);
expect(title0.toLowerCase(), contains('js'));
expect(sub0, isNotNull);
});
test('network lifecycle: start -> chunk -> complete', () async {
final id = controller.startNetwork(
apiType: APIType.rest,
method: HTTPVerb.get,
url: 'https://example.com',
requestHeaders: const {'A': '1'},
requestBodyPreview: 'req',
isStreaming: true,
);
expect(container.read(terminalStateProvider).entries, isNotEmpty);
controller.addNetworkChunk(
id,
BodyChunk(
ts: DateTime.now(),
text: 'chunk1',
sizeBytes: 6,
),
);
controller.completeNetwork(
id,
statusCode: 200,
responseHeaders: const {'B': '2'},
responseBodyPreview: 'ok',
duration: const Duration(milliseconds: 88),
);
final e = container.read(terminalStateProvider).entries.first;
expect(e.network?.phase, NetworkPhase.completed);
expect(e.level, TerminalLevel.info);
expect(e.network?.responseStatus, 200);
expect(e.network?.duration?.inMilliseconds, 88);
// Helpers
final title = controller.titleFor(e);
final sub = controller.subtitleFor(e);
expect(title, contains('GET'));
expect(title, contains('https://example.com'));
expect(sub, 'ok');
// Serialization should include status code
final ser = controller.serializeAll();
expect(ser, contains('200'));
});
test('network failure switches level to error', () {
final id = controller.startNetwork(
apiType: APIType.rest,
method: HTTPVerb.post,
url: 'https://api',
);
controller.failNetwork(id, 'timeout');
final e = container.read(terminalStateProvider).entries.first;
expect(e.level, TerminalLevel.error);
expect(e.network?.phase, NetworkPhase.failed);
expect(controller.subtitleFor(e), 'timeout');
});
test('completeNetwork maps 4xx/5xx to error level', () {
final id = controller.startNetwork(
apiType: APIType.rest,
method: HTTPVerb.get,
url: 'https://api',
);
controller.completeNetwork(id,
statusCode: 404, responseBodyPreview: 'nf');
final e = container.read(terminalStateProvider).entries.first;
expect(e.level, TerminalLevel.error);
expect(e.network?.responseStatus, 404);
expect(controller.subtitleFor(e), 'nf');
});
test('progress chunks update phase and accumulate data', () {
final id = controller.startNetwork(
apiType: APIType.rest,
method: HTTPVerb.get,
url: 'https://chunks',
isStreaming: true,
);
controller.addNetworkChunk(
id,
BodyChunk(ts: DateTime.now(), text: 'part1', sizeBytes: 5),
);
controller.addNetworkChunk(
id,
BodyChunk(ts: DateTime.now(), text: 'part2', sizeBytes: 5),
);
final e = container.read(terminalStateProvider).entries.first;
expect(e.network?.phase, NetworkPhase.progress);
expect(e.network?.chunks.length, 2);
expect(e.network?.chunks.first.text, 'part1');
});
test('entries are stored newest first and index maps ids', () {
controller.logSystem(category: 'a', message: 'm1');
controller.logSystem(category: 'b', message: 'm2');
final entries = container.read(terminalStateProvider).entries;
// Last logged appears first
expect(entries.first.system?.category, anyOf('a', 'b'));
final idxMap = container.read(terminalStateProvider).index;
expect(idxMap.containsKey(entries.first.id), isTrue);
expect(idxMap[entries.first.id], 0);
});
test('serializeAll with provided entries includes ISO timestamps', () {
final e1 = TerminalEntry(
id: 'x1',
ts: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
source: TerminalSource.system,
level: TerminalLevel.info,
system: SystemLogData(category: 'ui', message: 'hello'),
);
final e2 = TerminalEntry(
id: 'x2',
ts: DateTime.fromMillisecondsSinceEpoch(1000, isUtc: true),
source: TerminalSource.js,
level: TerminalLevel.error,
js: JsLogData(level: 'error', args: ['boom']),
);
final text = controller.serializeAll(entries: [e1, e2]);
expect(text, contains('[1970-01-01T00:00:00.000Z]'));
expect(text, contains('[1970-01-01T00:00:01.000Z]'));
expect(text, contains('System'));
expect(text, contains('JS error'));
});
});
group('TerminalState.copyWith', () {
test('returns same entries when no new entries provided', () {
final e1 = TerminalEntry(
id: 'id1',
source: TerminalSource.system,
level: TerminalLevel.info,
system: SystemLogData(category: 'ui', message: 'hello'),
);
final s1 = TerminalState(entries: [e1]);
final s2 = s1.copyWith();
expect(identical(s1.entries, s2.entries), isTrue);
expect(s2.index[e1.id], 0);
expect(s2.index.length, 1);
});
test('rebuilds index when entries list changes', () {
final e1 = TerminalEntry(
id: 'id1',
source: TerminalSource.system,
level: TerminalLevel.info,
system: SystemLogData(category: 'ui', message: 'hello'),
);
final e2 = TerminalEntry(
id: 'id2',
source: TerminalSource.js,
level: TerminalLevel.error,
js: JsLogData(level: 'error', args: ['boom']),
);
final s1 = TerminalState(entries: [e1]);
final s2 = s1.copyWith(entries: [e2, e1]);
expect(identical(s1.entries, s2.entries), isFalse);
expect(s2.entries, orderedEquals([e2, e1]));
expect(s2.index[e2.id], 0);
expect(s2.index[e1.id], 1);
expect(s2.index.length, 2);
});
});
}