import 'dart:io'; import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:ikar/core/plugins/plugin_host.dart'; import 'package:ikar/core/plugins/plugin_manifest.dart'; import 'package:ikar/core/plugins/plugin_models.dart'; import 'package:ikar/core/plugins/plugin_runtime.dart'; class _Host implements PluginHost { final messages = []; final notifications = []; @override String get args => 'synthetic'; @override Map get arguments => const {}; @override Map? get replyMessage => null; @override bool get isOnline => true; @override bool isActive = true; @override Future sendText(String text) async { messages.add(text); return 'synthetic-id'; } @override Future editText(String id, String text) async => messages.add('$id:$text'); @override Future notify(String text) async => notifications.add(text); @override Future?> getPeer() async => null; @override Future sendFile(Uint8List bytes, {required String filename}) async {} @override Future sendPhoto( Uint8List bytes, { required String filename, required String caption, }) async {} } PluginCommandDescriptor _command(String code, Set granted) { final manifest = PluginManifest.fromJson({ 'schemaVersion': 1, 'id': 'dev.example.runtime', 'name': 'Synthetic', 'version': '1.0.0', 'apiVersion': 1, 'description': 'Runtime test', 'author': 'Test', 'main': 'main.js', 'permissions': ['chat.write', 'chat.edit', 'ui.notify'], 'commands': [ {'name': '/synthetic', 'description': 'Test', 'handler': 'run'}, ], }); return PluginCommandDescriptor( plugin: PluginDescriptor( manifest: manifest, origin: PluginOrigin.installed, enabled: true, grantedPermissions: granted, loadModules: () async => {'main.js': code}, signatureStatus: PluginSignatureStatus.unsigned, ), command: manifest.commands.single, ); } void main() { TestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async { final library = Platform.environment['QLYRA_FJS_LIBRARY'] ?? 'build/native-tests/fjs/${Platform.isWindows ? 'fjs.dll' : Platform.isMacOS ? 'libfjs.dylib' : 'libfjs.so'}'; if (!File(library).existsSync()) { throw StateError( 'Run dart run tool/prepare_plugin_runtime.dart before native plugin tests', ); } await PluginRuntime.initialize( externalLibrary: ExternalLibrary.open(File(library).absolute.path), ); }); test( 'executes a JavaScript plugin with the compatible API and awaits editing', () async { final host = _Host(); await PluginRuntime.run( _command( ''' import { chat } from 'komet:api'; export async function run() { const id = await chat.sendText('synthetic'); await chat.editText(id, 'edited'); } ''', {PluginPermission.chatWrite, PluginPermission.chatEdit}, ), host, ); expect(host.messages, ['synthetic', 'synthetic-id:edited']); }, ); test('denies sending without an explicit grant', () async { final host = _Host(); await PluginRuntime.run( _command( ''' import { chat, ui } from 'komet:api'; export async function run() { try { await chat.sendText('forbidden'); } catch (_) { await ui.notify('denied'); } } ''', {PluginPermission.uiNotify}, ), host, ); expect(host.messages, isEmpty); expect(host.notifications, ['denied']); }); test('a closed chat cannot receive plugin messages', () async { final host = _Host()..isActive = false; try { await PluginRuntime.run( _command( ''' import { chat } from 'komet:api'; export async function run() { await chat.sendText('forbidden'); } ''', {PluginPermission.chatWrite}, ), host, ); } catch (_) {} expect(host.messages, isEmpty); }); }