- Rename mobile app directory to client throughout the project - Restructure client app code into feature-based organization - Move code to src/features/workspaces/domain/presentation - Move services to src/integrations/ (mattermost, workspace, proto_socket) - Add app bootstrap and main entry point - Add push notification integration (Mattermost push client/host/plugin) - Add proto socket integration for real-time communication - Add integration tests for push and socket components - Archive old milestone: client-integration-standardization.md - Add new workflow core milestone: roadmap-driven-agent-ops-automation.md - Update agent-ops rules (project, core, mobile, contracts, workspace-ops) - Update roadmap files (ROADMAP.md, current.md, phase PHASE.md files) - Update bin scripts (build, dev, lint, test) - Add test environments documentation - Update contracts notes for Flutter Core API
89 lines
2.7 KiB
Dart
89 lines
2.7 KiB
Dart
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:nomadcode_app/src/integrations/proto_socket/proto_socket_endpoint_config.dart';
|
|
import 'package:nomadcode_app/src/integrations/proto_socket/proto_socket_lifecycle.dart';
|
|
|
|
class _FakeConnector implements ProtoSocketConnector {
|
|
int connectCalls = 0;
|
|
int disconnectCalls = 0;
|
|
Object? throwOnConnect;
|
|
|
|
@override
|
|
Future<void> connect(ProtoSocketEndpointConfig config) async {
|
|
connectCalls += 1;
|
|
final err = throwOnConnect;
|
|
if (err != null) throw err;
|
|
}
|
|
|
|
@override
|
|
Future<void> disconnect() async {
|
|
disconnectCalls += 1;
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
const config = ProtoSocketEndpointConfig(
|
|
host: 'core.example.com',
|
|
port: 443,
|
|
secure: true,
|
|
);
|
|
|
|
test('happy path emits connecting -> connected -> disconnected', () async {
|
|
final fake = _FakeConnector();
|
|
final lifecycle = ProtoSocketLifecycle(connector: fake);
|
|
final states = <ProtoSocketConnectionState>[];
|
|
final sub = lifecycle.stateStream.listen(states.add);
|
|
|
|
expect(lifecycle.state, ProtoSocketConnectionState.disconnected);
|
|
|
|
await lifecycle.connect(config);
|
|
expect(lifecycle.state, ProtoSocketConnectionState.connected);
|
|
expect(fake.connectCalls, equals(1));
|
|
|
|
await lifecycle.disconnect();
|
|
expect(lifecycle.state, ProtoSocketConnectionState.disconnected);
|
|
expect(fake.disconnectCalls, equals(1));
|
|
|
|
await Future<void>.delayed(Duration.zero);
|
|
await sub.cancel();
|
|
await lifecycle.dispose();
|
|
|
|
expect(states, [
|
|
ProtoSocketConnectionState.connecting,
|
|
ProtoSocketConnectionState.connected,
|
|
ProtoSocketConnectionState.disconnected,
|
|
]);
|
|
});
|
|
|
|
test('connect failure transitions to failed and records error', () async {
|
|
final fake = _FakeConnector()..throwOnConnect = StateError('boom');
|
|
final lifecycle = ProtoSocketLifecycle(connector: fake);
|
|
|
|
await expectLater(lifecycle.connect(config), throwsA(isA<StateError>()));
|
|
expect(lifecycle.state, ProtoSocketConnectionState.failed);
|
|
expect(lifecycle.lastError, isA<StateError>());
|
|
|
|
await lifecycle.dispose();
|
|
});
|
|
|
|
test('connect is a no-op when already connecting or connected', () async {
|
|
final fake = _FakeConnector();
|
|
final lifecycle = ProtoSocketLifecycle(connector: fake);
|
|
|
|
await lifecycle.connect(config);
|
|
await lifecycle.connect(config);
|
|
|
|
expect(fake.connectCalls, equals(1));
|
|
await lifecycle.dispose();
|
|
});
|
|
|
|
test('disconnect from disconnected state is a no-op', () async {
|
|
final fake = _FakeConnector();
|
|
final lifecycle = ProtoSocketLifecycle(connector: fake);
|
|
|
|
await lifecycle.disconnect();
|
|
expect(fake.disconnectCalls, equals(0));
|
|
expect(lifecycle.state, ProtoSocketConnectionState.disconnected);
|
|
|
|
await lifecycle.dispose();
|
|
});
|
|
}
|