Skip to content

Commit 8bf4b3d

Browse files
committed
tests: add all tests for terminal related features(coverage: 100)
1 parent 8114cc2 commit 8bf4b3d

File tree

5 files changed

+652
-0
lines changed

5 files changed

+652
-0
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import 'package:test/test.dart';
2+
import 'package:apidash/consts.dart';
3+
import 'package:apidash/models/terminal/models.dart';
4+
import 'package:apidash_core/apidash_core.dart';
5+
6+
void main() {
7+
group('TerminalEntry model', () {
8+
test('copyWith preserves immutability and updates fields', () {
9+
final entry = TerminalEntry(
10+
id: 'e1',
11+
source: TerminalSource.system,
12+
level: TerminalLevel.info,
13+
system: SystemLogData(category: 'ui', message: 'hello'),
14+
);
15+
16+
final ts = DateTime.fromMillisecondsSinceEpoch(1000);
17+
final updated = entry.copyWith(
18+
ts: ts,
19+
level: TerminalLevel.warn,
20+
system: SystemLogData(category: 'io', message: 'updated'),
21+
);
22+
23+
expect(updated.id, 'e1');
24+
expect(updated.ts, ts);
25+
expect(updated.level, TerminalLevel.warn);
26+
expect(updated.system?.category, 'io');
27+
expect(entry.system?.category, 'ui');
28+
});
29+
30+
test('supports js and network data variants', () {
31+
final jsEntry = TerminalEntry(
32+
id: 'j1',
33+
source: TerminalSource.js,
34+
level: TerminalLevel.error,
35+
js: JsLogData(level: 'error', args: ['a', 'b'], stack: 'trace'),
36+
);
37+
expect(jsEntry.js, isNotNull);
38+
expect(jsEntry.system, isNull);
39+
40+
final netEntry = TerminalEntry(
41+
id: 'n1',
42+
source: TerminalSource.network,
43+
level: TerminalLevel.info,
44+
network: NetworkLogData(
45+
phase: NetworkPhase.started,
46+
apiType: APIType.rest,
47+
method: HTTPVerb.get,
48+
url: 'https://api.apidash.dev',
49+
),
50+
);
51+
expect(netEntry.network, isNotNull);
52+
expect(netEntry.js, isNull);
53+
});
54+
});
55+
56+
group('NetworkLogData model', () {
57+
test('copyWith updates fields and keeps others', () {
58+
final n = NetworkLogData(
59+
phase: NetworkPhase.started,
60+
apiType: APIType.rest,
61+
method: HTTPVerb.post,
62+
url: 'https://api.apidash.dev',
63+
requestHeaders: const {'A': '1'},
64+
requestBodyPreview: '{x}',
65+
);
66+
67+
final completedAt = DateTime.fromMillisecondsSinceEpoch(5000);
68+
final c = n.copyWith(
69+
phase: NetworkPhase.completed,
70+
responseStatus: 200,
71+
responseHeaders: const {'B': '2'},
72+
responseBodyPreview: 'ok',
73+
duration: const Duration(milliseconds: 123),
74+
isStreaming: true,
75+
completedAt: completedAt,
76+
);
77+
78+
expect(c.phase, NetworkPhase.completed);
79+
expect(c.responseStatus, 200);
80+
expect(c.responseHeaders, {'B': '2'});
81+
expect(c.requestHeaders, {'A': '1'});
82+
expect(c.requestBodyPreview, '{x}');
83+
expect(c.responseBodyPreview, 'ok');
84+
expect(c.duration?.inMilliseconds, 123);
85+
expect(c.isStreaming, true);
86+
expect(c.completedAt, completedAt);
87+
});
88+
89+
test('copyWith preserves phase when not provided', () {
90+
final n = NetworkLogData(
91+
phase: NetworkPhase.started,
92+
apiType: APIType.rest,
93+
method: HTTPVerb.get,
94+
url: 'https://api.apidash.dev',
95+
);
96+
97+
// Do not pass phase; update another field
98+
final c = n.copyWith(responseStatus: 201);
99+
100+
expect(c.phase, NetworkPhase.started);
101+
expect(c.responseStatus, 201);
102+
// Verify some other fields are preserved
103+
expect(c.apiType, APIType.rest);
104+
expect(c.method, HTTPVerb.get);
105+
expect(c.url, 'https://api.apidash.dev');
106+
});
107+
108+
test('BodyChunk stores timestamp, text and size', () {
109+
final t = DateTime.fromMillisecondsSinceEpoch(42);
110+
final b = BodyChunk(ts: t, text: 'hello', sizeBytes: 5);
111+
expect(b.ts, t);
112+
expect(b.text, 'hello');
113+
expect(b.sizeBytes, 5);
114+
});
115+
});
116+
117+
group('JsLogData & SystemLogData', () {
118+
test('JsLogData holds args, level and optional context', () {
119+
final j = JsLogData(level: 'warn', args: ['x', 'y'], context: 'pre');
120+
expect(j.level, 'warn');
121+
expect(j.args, ['x', 'y']);
122+
expect(j.context, 'pre');
123+
expect(j.stack, isNull);
124+
});
125+
126+
test('SystemLogData stores category, message and stack', () {
127+
final s = SystemLogData(category: 'provider', message: 'm', stack: 'st');
128+
expect(s.category, 'provider');
129+
expect(s.message, 'm');
130+
expect(s.stack, 'st');
131+
});
132+
});
133+
}
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:flutter_riverpod/flutter_riverpod.dart';
3+
import 'package:apidash/providers/terminal_providers.dart';
4+
import 'package:apidash/models/terminal/models.dart';
5+
import 'package:apidash/consts.dart';
6+
import 'package:apidash_core/apidash_core.dart';
7+
8+
void main() {
9+
group('TerminalController', () {
10+
late ProviderContainer container;
11+
late TerminalController controller;
12+
13+
setUp(() {
14+
container = ProviderContainer();
15+
controller = container.read(terminalStateProvider.notifier);
16+
});
17+
18+
tearDown(() {
19+
container.dispose();
20+
});
21+
22+
test('initial state is empty', () {
23+
final state = container.read(terminalStateProvider);
24+
expect(state.entries, isEmpty);
25+
expect(state.index, isEmpty);
26+
});
27+
28+
test('log system entries and clear', () {
29+
controller.logSystem(category: 'ui', message: 'opened');
30+
controller.logSystem(
31+
category: 'provider', message: 'updated', level: TerminalLevel.warn);
32+
final state = container.read(terminalStateProvider);
33+
expect(state.entries.length, 2);
34+
expect(state.entries.first.system?.category, anyOf('ui', 'provider'));
35+
36+
// serialization has timestamps, uppercase level and title-cased source
37+
final text = controller.serializeAll();
38+
expect(text, contains('INFO'));
39+
expect(text, contains('System'));
40+
expect(text.toLowerCase(), contains('opened'));
41+
42+
controller.clear();
43+
expect(container.read(terminalStateProvider).entries, isEmpty);
44+
});
45+
46+
test('JS logs map levels and include args', () {
47+
controller.logJs(level: 'log', args: ['hello']);
48+
controller.logJs(level: 'warn', args: ['warn']);
49+
controller.logJs(level: 'error', args: ['err'], context: 'preRequest');
50+
final state = container.read(terminalStateProvider);
51+
expect(state.entries.length, 3);
52+
final levels = state.entries.map((e) => e.level).toList();
53+
expect(levels.contains(TerminalLevel.info), isTrue);
54+
expect(levels.contains(TerminalLevel.warn), isTrue);
55+
expect(levels.contains(TerminalLevel.error), isTrue);
56+
57+
final title0 = controller.titleFor(state.entries.first);
58+
final sub0 = controller.subtitleFor(state.entries.first);
59+
expect(title0.toLowerCase(), contains('js'));
60+
expect(sub0, isNotNull);
61+
});
62+
63+
test('network lifecycle: start -> chunk -> complete', () async {
64+
final id = controller.startNetwork(
65+
apiType: APIType.rest,
66+
method: HTTPVerb.get,
67+
url: 'https://example.com',
68+
requestHeaders: const {'A': '1'},
69+
requestBodyPreview: 'req',
70+
isStreaming: true,
71+
);
72+
expect(container.read(terminalStateProvider).entries, isNotEmpty);
73+
74+
controller.addNetworkChunk(
75+
id,
76+
BodyChunk(
77+
ts: DateTime.now(),
78+
text: 'chunk1',
79+
sizeBytes: 6,
80+
),
81+
);
82+
83+
controller.completeNetwork(
84+
id,
85+
statusCode: 200,
86+
responseHeaders: const {'B': '2'},
87+
responseBodyPreview: 'ok',
88+
duration: const Duration(milliseconds: 88),
89+
);
90+
91+
final e = container.read(terminalStateProvider).entries.first;
92+
expect(e.network?.phase, NetworkPhase.completed);
93+
expect(e.level, TerminalLevel.info);
94+
expect(e.network?.responseStatus, 200);
95+
expect(e.network?.duration?.inMilliseconds, 88);
96+
97+
// Helpers
98+
final title = controller.titleFor(e);
99+
final sub = controller.subtitleFor(e);
100+
expect(title, contains('GET'));
101+
expect(title, contains('https://example.com'));
102+
expect(sub, 'ok');
103+
104+
// Serialization should include status code
105+
final ser = controller.serializeAll();
106+
expect(ser, contains('200'));
107+
});
108+
109+
test('network failure switches level to error', () {
110+
final id = controller.startNetwork(
111+
apiType: APIType.rest,
112+
method: HTTPVerb.post,
113+
url: 'https://api',
114+
);
115+
controller.failNetwork(id, 'timeout');
116+
final e = container.read(terminalStateProvider).entries.first;
117+
expect(e.level, TerminalLevel.error);
118+
expect(e.network?.phase, NetworkPhase.failed);
119+
expect(controller.subtitleFor(e), 'timeout');
120+
});
121+
122+
test('completeNetwork maps 4xx/5xx to error level', () {
123+
final id = controller.startNetwork(
124+
apiType: APIType.rest,
125+
method: HTTPVerb.get,
126+
url: 'https://api',
127+
);
128+
controller.completeNetwork(id,
129+
statusCode: 404, responseBodyPreview: 'nf');
130+
final e = container.read(terminalStateProvider).entries.first;
131+
expect(e.level, TerminalLevel.error);
132+
expect(e.network?.responseStatus, 404);
133+
expect(controller.subtitleFor(e), 'nf');
134+
});
135+
136+
test('progress chunks update phase and accumulate data', () {
137+
final id = controller.startNetwork(
138+
apiType: APIType.rest,
139+
method: HTTPVerb.get,
140+
url: 'https://chunks',
141+
isStreaming: true,
142+
);
143+
controller.addNetworkChunk(
144+
id,
145+
BodyChunk(ts: DateTime.now(), text: 'part1', sizeBytes: 5),
146+
);
147+
controller.addNetworkChunk(
148+
id,
149+
BodyChunk(ts: DateTime.now(), text: 'part2', sizeBytes: 5),
150+
);
151+
final e = container.read(terminalStateProvider).entries.first;
152+
expect(e.network?.phase, NetworkPhase.progress);
153+
expect(e.network?.chunks.length, 2);
154+
expect(e.network?.chunks.first.text, 'part1');
155+
});
156+
157+
test('entries are stored newest first and index maps ids', () {
158+
controller.logSystem(category: 'a', message: 'm1');
159+
controller.logSystem(category: 'b', message: 'm2');
160+
final entries = container.read(terminalStateProvider).entries;
161+
// Last logged appears first
162+
expect(entries.first.system?.category, anyOf('a', 'b'));
163+
final idxMap = container.read(terminalStateProvider).index;
164+
expect(idxMap.containsKey(entries.first.id), isTrue);
165+
expect(idxMap[entries.first.id], 0);
166+
});
167+
168+
test('serializeAll with provided entries includes ISO timestamps', () {
169+
final e1 = TerminalEntry(
170+
id: 'x1',
171+
ts: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
172+
source: TerminalSource.system,
173+
level: TerminalLevel.info,
174+
system: SystemLogData(category: 'ui', message: 'hello'),
175+
);
176+
final e2 = TerminalEntry(
177+
id: 'x2',
178+
ts: DateTime.fromMillisecondsSinceEpoch(1000, isUtc: true),
179+
source: TerminalSource.js,
180+
level: TerminalLevel.error,
181+
js: JsLogData(level: 'error', args: ['boom']),
182+
);
183+
final text = controller.serializeAll(entries: [e1, e2]);
184+
expect(text, contains('[1970-01-01T00:00:00.000Z]'));
185+
expect(text, contains('[1970-01-01T00:00:01.000Z]'));
186+
expect(text, contains('System'));
187+
expect(text, contains('JS error'));
188+
});
189+
});
190+
191+
group('TerminalState.copyWith', () {
192+
test('returns same entries when no new entries provided', () {
193+
final e1 = TerminalEntry(
194+
id: 'id1',
195+
source: TerminalSource.system,
196+
level: TerminalLevel.info,
197+
system: SystemLogData(category: 'ui', message: 'hello'),
198+
);
199+
final s1 = TerminalState(entries: [e1]);
200+
final s2 = s1.copyWith();
201+
202+
expect(identical(s1.entries, s2.entries), isTrue);
203+
expect(s2.index[e1.id], 0);
204+
expect(s2.index.length, 1);
205+
});
206+
207+
test('rebuilds index when entries list changes', () {
208+
final e1 = TerminalEntry(
209+
id: 'id1',
210+
source: TerminalSource.system,
211+
level: TerminalLevel.info,
212+
system: SystemLogData(category: 'ui', message: 'hello'),
213+
);
214+
final e2 = TerminalEntry(
215+
id: 'id2',
216+
source: TerminalSource.js,
217+
level: TerminalLevel.error,
218+
js: JsLogData(level: 'error', args: ['boom']),
219+
);
220+
221+
final s1 = TerminalState(entries: [e1]);
222+
final s2 = s1.copyWith(entries: [e2, e1]);
223+
224+
expect(identical(s1.entries, s2.entries), isFalse);
225+
expect(s2.entries, orderedEquals([e2, e1]));
226+
expect(s2.index[e2.id], 0);
227+
expect(s2.index[e1.id], 1);
228+
expect(s2.index.length, 2);
229+
});
230+
});
231+
}

0 commit comments

Comments
 (0)