xworkmate-app/test/runtime/codex_runtime_test.dart
Haitao Pan a6699beff3 feat: integrate Codex CLI as built-in code agent
- Add CodexRuntime for process management and JSON-RPC communication
- Add CodexConfigBridge for AI Gateway configuration
- Add ModeSwitcher for OpenClaw Gateway mode switching (local/remote/offline)
- Add AgentRegistry for agent registration and discovery
- Add RuntimeCoordinator for unified coordination
- Add Rust FFI bindings for native integration
- Add comprehensive test coverage

Phase 1-4 features:
- Configuration bridging to AI Gateway
- Mode switching between local/remote/offline
- Agent registration protocol
- Cloud memory sync capability
- Offline fallback support

CI/CD:
- GitHub Actions workflow for Rust FFI build
- Build scripts for macOS universal binary
- Integration with Flutter build process

Co-authored-by: Codex CLI Integration <codex@openai.com>
2026-03-14 00:10:27 +08:00

151 lines
4.2 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/runtime/codex_runtime.dart';
void main() {
group('CodexSandboxMode', () {
test('has correct values', () {
expect(CodexSandboxMode.readOnly.value, equals('read-only'));
expect(CodexSandboxMode.workspaceWrite.value, equals('workspace-write'));
expect(CodexSandboxMode.dangerFullAccess.value, equals('danger-full-access'));
});
});
group('CodexApprovalPolicy', () {
test('has correct values', () {
expect(CodexApprovalPolicy.suggest.value, equals('suggest'));
expect(CodexApprovalPolicy.autoEdit.value, equals('auto-edit'));
expect(CodexApprovalPolicy.fullAuto.value, equals('full-auto'));
});
});
group('CodexThread', () {
test('fromJson creates correct object', () {
final json = {
'id': 'thread-123',
'path': '/path/to/thread',
'ephemeral': true,
'createdAt': '2024-01-01T00:00:00Z',
};
final thread = CodexThread.fromJson(json);
expect(thread.id, equals('thread-123'));
expect(thread.path, equals('/path/to/thread'));
expect(thread.ephemeral, isTrue);
expect(thread.createdAt, isNotNull);
});
test('toJson produces correct output', () {
final thread = CodexThread(
id: 'thread-456',
path: '/another/path',
ephemeral: false,
);
final json = thread.toJson();
expect(json['id'], equals('thread-456'));
expect(json['path'], equals('/another/path'));
expect(json['ephemeral'], isFalse);
});
});
group('CodexRpcError', () {
test('fromJson creates correct object', () {
final json = {
'code': -32000,
'message': 'Server error',
'data': {'details': 'test'},
};
final error = CodexRpcError.fromJson(json);
expect(error.code, equals(-32000));
expect(error.message, equals('Server error'));
expect(error.data, isNotNull);
});
test('toString formats correctly', () {
final error = CodexRpcError(code: -1, message: 'Test error');
expect(error.toString(), equals('CodexRpcError(-1): Test error'));
});
});
group('CodexTurnEvent', () {
test('fromNotification creates correct event', () {
final notification = CodexNotificationEvent(
method: 'item/agentMessage/delta',
params: {
'threadId': 'thread-1',
'turnId': 'turn-1',
'itemId': 'item-1',
'delta': 'Hello ',
},
);
final event = CodexTurnEvent.fromNotification(notification);
expect(event.type, equals('item/agentMessage/delta'));
expect(event.threadId, equals('thread-1'));
expect(event.turnId, equals('turn-1'));
expect(event.textDelta, equals('Hello '));
expect(event.isTextDelta, isTrue);
});
test('isTextDelta returns false for non-delta events', () {
final notification = CodexNotificationEvent(
method: 'turn/completed',
params: {'threadId': 'thread-1'},
);
final event = CodexTurnEvent.fromNotification(notification);
expect(event.isTextDelta, isFalse);
});
});
group('CodexRuntime', () {
late CodexRuntime runtime;
setUp(() {
runtime = CodexRuntime();
});
tearDown(() async {
await runtime.stop();
});
test('initial state is disconnected', () {
expect(runtime.state, equals(CodexConnectionState.disconnected));
expect(runtime.isConnected, isFalse);
expect(runtime.isReady, isFalse);
});
test('findCodexBinary returns null when not found', () async {
final path = await runtime.findCodexBinary();
// May or may not find codex depending on environment
// Just check it doesn't throw
expect(path, anyOf(isNull, isA<String>()));
});
test('request throws when not connected', () async {
expect(
() => runtime.request('initialize', params: {}),
throwsA(isA<StateError>()),
);
});
test('stop is idempotent', () async {
// Should not throw when called on disconnected runtime
await runtime.stop();
await runtime.stop();
expect(runtime.isConnected, isFalse);
});
});
}