feat(useragent): vibe-coded access list

This commit is contained in:
hdbg
2026-03-25 11:52:10 +01:00
parent bbf8a8019c
commit 7f8b9cc63e
20 changed files with 1462 additions and 101 deletions

View File

@@ -0,0 +1,69 @@
import 'package:arbiter/proto/client.pb.dart';
import 'package:arbiter/proto/evm.pb.dart';
import 'package:arbiter/proto/user_agent.pb.dart';
import 'package:arbiter/providers/evm/evm.dart';
import 'package:arbiter/providers/sdk_clients/list.dart';
import 'package:arbiter/providers/sdk_clients/wallet_access.dart';
import 'package:arbiter/screens/dashboard/clients/details/client_details.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class _FakeEvm extends Evm {
_FakeEvm(this.wallets);
final List<WalletEntry> wallets;
@override
Future<List<WalletEntry>?> build() async => wallets;
}
class _FakeWalletAccessRepository implements ClientWalletAccessRepository {
@override
Future<Set<int>> fetchSelectedWalletIds(int clientId) async => {1};
@override
Future<void> saveSelectedWalletIds(int clientId, Set<int> walletIds) async {}
}
void main() {
testWidgets('renders client summary and wallet access controls', (
tester,
) async {
final client = SdkClientEntry(
id: 42,
createdAt: 1,
info: ClientInfo(
name: 'Safe Wallet SDK',
version: '1.3.0',
description: 'Primary signing client',
),
pubkey: List.filled(32, 17),
);
final wallets = [
WalletEntry(address: List.filled(20, 1)),
WalletEntry(address: List.filled(20, 2)),
];
await tester.pumpWidget(
ProviderScope(
overrides: [
sdkClientsProvider.overrideWith((ref) async => [client]),
evmProvider.overrideWith(() => _FakeEvm(wallets)),
clientWalletAccessRepositoryProvider.overrideWithValue(
_FakeWalletAccessRepository(),
),
],
child: const MaterialApp(home: ClientDetailsScreen(clientId: 42)),
),
);
await tester.pumpAndSettle();
expect(find.text('Safe Wallet SDK'), findsOneWidget);
expect(find.text('Wallet access'), findsOneWidget);
expect(find.textContaining('0x0101'), findsOneWidget);
expect(find.widgetWithText(FilledButton, 'Save changes'), findsOneWidget);
});
}

View File

@@ -0,0 +1,105 @@
import 'package:arbiter/providers/sdk_clients/wallet_access.dart';
import 'package:hooks_riverpod/experimental/mutation.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
class _SuccessRepository implements ClientWalletAccessRepository {
Set<int>? savedWalletIds;
@override
Future<Set<int>> fetchSelectedWalletIds(int clientId) async => {1};
@override
Future<void> saveSelectedWalletIds(int clientId, Set<int> walletIds) async {
savedWalletIds = walletIds;
}
}
class _FailureRepository implements ClientWalletAccessRepository {
@override
Future<Set<int>> fetchSelectedWalletIds(int clientId) async => const {};
@override
Future<void> saveSelectedWalletIds(int clientId, Set<int> walletIds) async {
throw UnsupportedError('Not supported yet: $walletIds');
}
}
void main() {
test('save updates the original selection after toggles', () async {
final repository = _SuccessRepository();
final container = ProviderContainer(
overrides: [
clientWalletAccessRepositoryProvider.overrideWithValue(repository),
],
);
addTearDown(container.dispose);
final controller = container.read(
clientWalletAccessControllerProvider(42).notifier,
);
await container.read(clientWalletAccessSelectionProvider(42).future);
controller.toggleWallet(2);
expect(
container
.read(clientWalletAccessControllerProvider(42))
.selectedWalletIds,
{1, 2},
);
expect(
container.read(clientWalletAccessControllerProvider(42)).hasChanges,
isTrue,
);
await executeSaveClientWalletAccess(container, clientId: 42);
expect(repository.savedWalletIds, {1, 2});
expect(
container
.read(clientWalletAccessControllerProvider(42))
.originalWalletIds,
{1, 2},
);
expect(
container.read(clientWalletAccessControllerProvider(42)).hasChanges,
isFalse,
);
});
test('save failure preserves edits and exposes a mutation error', () async {
final container = ProviderContainer(
overrides: [
clientWalletAccessRepositoryProvider.overrideWithValue(
_FailureRepository(),
),
],
);
addTearDown(container.dispose);
final controller = container.read(
clientWalletAccessControllerProvider(42).notifier,
);
await container.read(clientWalletAccessSelectionProvider(42).future);
controller.toggleWallet(3);
await expectLater(
executeSaveClientWalletAccess(container, clientId: 42),
throwsUnsupportedError,
);
expect(
container
.read(clientWalletAccessControllerProvider(42))
.selectedWalletIds,
{3},
);
expect(
container.read(clientWalletAccessControllerProvider(42)).hasChanges,
isTrue,
);
expect(
container.read(saveClientWalletAccessMutation(42)),
isA<MutationError<void>>(),
);
});
}