ci: run analyze + tests on every push/PR; make the suite real

- delete the stock Flutter counter widget_test (always failed) and the
  lightning invoice tests (network-dependent, and the feature is off)
- fix rotted assertions: removed-chain wallet types (solana, tron, zano,
  decred) and mainnet-only BTC/LTC pattern expectations
- new test.yml workflow: pub get + analyze + test on push/PR to dev/main
- analysis_options: drop excludes for deleted paths, stop excluding the
  five live coin facades, enable empty_catches and 7 other correctness
  lints (still 0 analyzer errors)
- integration_test_runner: clean hash_wallet data dirs, not cake_wallet
This commit is contained in:
jwinterm
2026-08-15 16:28:44 -04:00
parent 08fb925cea
commit 77ae91e695
8 changed files with 93 additions and 151 deletions
+77
View File
@@ -0,0 +1,77 @@
name: Tests and analysis
# Fast, pure-Dart verification: no native builds, no signing secrets.
# Runs on every push and PR (unlike the manual-dispatch build workflows,
# this finishes in minutes and needs nothing beyond pub get).
on:
push:
branches: [dev, main]
pull_request:
branches: [dev, main]
workflow_dispatch:
concurrency:
group: test-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
test:
runs-on: ubuntu-latest
container:
# Same prebuilt image as the build workflows (bundles Flutter 3.32.0).
image: ghcr.io/cake-tech/cake_wallet:debian13-flutter3.32.0-ndkr28-go1.24.1-ruststablenightly
steps:
- name: Fix Actions HOME (writeable for ~/.cache)
run: echo "HOME=/root" >> $GITHUB_ENV
- uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Configure git inside the container
run: git config --global --add safe.directory '*'
# pubspec.yaml has path deps under scripts/ that must exist before
# pub get; fetch the same prebuilt bundles the build workflows use.
- name: Fetch prebuilt torch_dart
run: |
set -x -e
pushd scripts
rm -rf torch_dart torch_dart.tar.gz
wget -q https://github.com/MrCyjaneK/torch_dart/releases/download/v1.0.17/torch_dart-v1.0.17.tar.gz -O torch_dart.tar.gz
mkdir torch_dart
tar -xzf torch_dart.tar.gz -C torch_dart
rm torch_dart.tar.gz
popd
- name: Fetch prebuilt reown_flutter
run: |
set -x -e
pushd scripts
rm -rf reown_flutter reown_flutter.tar.gz
wget -q https://github.com/cake-tech/reown_flutter/releases/download/v0.0.4/reown_flutter-v0.0.4.tar.gz -O reown_flutter.tar.gz
mkdir reown_flutter
tar -xzf reown_flutter.tar.gz -C reown_flutter
rm reown_flutter.tar.gz
popd
- name: Clone BitBox Flutter
run: |
set -x -e
pushd scripts
./build_bitbox_flutter.sh
popd
- name: flutter pub get
run: flutter pub get
- name: flutter analyze (errors are fatal, existing warnings are not)
run: flutter analyze lib --no-fatal-infos --no-fatal-warnings
- name: flutter test
run: flutter test
+8 -14
View File
@@ -7,25 +7,11 @@ analyzer:
scripts/**,
lib/**.g.dart,
cw_core/lib/**.g.dart,
cw_haven/lib/**.g.dart,
cw_monero/lib/**.g.dart,
lib/generated/*.dart,
cw_monero/ios/External/**,
cw_shared_external/**,
shared_external/**,
lib/bitcoin/cw_bitcoin.dart,
lib/bitcoin_cash/cw_bitcoin_cash.dart,
lib/ethereum/cw_ethereum.dart,
lib/haven/cw_haven.dart,
lib/monero/cw_monero.dart,
lib/nano/cw_nano.dart,
lib/polygon/cw_polygon.dart,
lib/solana/cw_solana.dart,
lib/tron/cw_tron.dart,
lib/wownero/cw_wownero.dart,
lib/zano/cw_zano.dart,
lib/base/cw_base.dart,
lib/arbitrum/cw_arbitrum.dart,
]
language:
strict-casts: true
@@ -36,6 +22,14 @@ linter:
- cancel_subscriptions
- always_declare_return_types
- prefer_final_fields
- empty_catches
- await_only_futures
- valid_regexps
- unrelated_type_equality_checks
- hash_and_equals
- test_types_in_equals
- throw_in_finally
- control_flow_in_finally
formatter:
+5 -1
View File
@@ -10,8 +10,12 @@ MAX_PARALLEL_TESTS=${MAX_PARALLEL_TESTS:-2}
TEST_TIMEOUT=${TEST_TIMEOUT:-600}
RETRY_COUNT=${RETRY_COUNT:-1}
# Data directories to clean
# Data directories to clean (current app dirs plus legacy pre-rename ones,
# since root_dir.dart migrates legacy data into the current dir on startup)
DATA_DIRS=(
"$HOME/.local/share/com.suchsoftware.hashwallet"
"$HOME/Documents/hash_wallet"
"$HOME/.config/hash_wallet"
"$HOME/.local/share/com.example.cake_wallet"
"$HOME/Documents/cake_wallet"
"$HOME/.config/cake_wallet"
+3 -3
View File
@@ -12,7 +12,7 @@ void main() {
test('returns correct pattern for Bitcoin', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.btc);
expect(pattern, isNotEmpty);
expect(pattern, contains('(bc|tb)1q'));
expect(pattern, contains('bc1q'));
});
test('returns correct pattern for Ethereum', () {
@@ -30,8 +30,8 @@ void main() {
test('returns correct pattern for Litecoin', () {
final pattern = AddressValidator.getPattern(CryptoCurrency.ltc);
expect(pattern, isNotEmpty);
expect(pattern,
contains('(bc|tb|ltc)1q[ac-hj-np-z02-9]{25,39}|(ltc|t)mweb1q[ac-hj-np-z02-9]{90,120}'));
expect(pattern, contains('ltc1q[ac-hj-np-z02-9]{25,39}'));
expect(pattern, contains('(ltc|t)mweb1q[ac-hj-np-z02-9]{90,120}'));
});
test('returns empty string for unknown currency', () {
@@ -1,31 +0,0 @@
import "package:hash_wallet/core/lightning_invoice_service.dart";
import "package:cw_core/lnurl.dart";
import "package:cw_core/utils/proxy_wrapper.dart";
import "package:cw_core/utils/tor/abstract.dart";
import "package:flutter_test/flutter_test.dart";
Future<void> main() async {
CakeTor.instance = await CakeTorInstance.getInstance();
group("lightning_invoice_service", () {
test("Should resolve an empty Bolt11 invoice konsti@cake.cash", () async {
final invoice = await getBolt11FromLightingAddress("konsti@cake.cash");
expect(invoice, isNotNull);
expect(isBolt11ZeroInvoice(invoice!), isTrue);
});
test("Should not resolve an empty Bolt11 invoice viksharma@cluborange.org", () async {
final invoice = await getBolt11FromLightingAddress("viksharma@cluborange.org");
expect(invoice, isNull);
});
test("Should resolve an Bolt11 invoice viksharma@cluborange.org", () async {
final invoice =
await getBolt11FromLightingAddress("viksharma@cluborange.org", amount: 1000000000);
expect(invoice, isNotNull);
});
});
}
@@ -171,30 +171,6 @@ void main() {
});
});
group('Solana Address Detection', () {
test('detects Solana address', () {
const address = '7TSTXnQZxQDA4JoNqs4DqVqE7oNWk2kmHkMT6DD6vw2S';
final result = UniversalAddressDetector.detectAddress(address);
expect(result.isValid, true);
expect(result.detectedCurrency, CryptoCurrency.sol);
expect(result.detectedWalletType, WalletType.solana);
expect(result.address, address);
});
});
group('Tron Address Detection', () {
test('detects Tron address', () {
const address = 'TF7yQKp7pwLBSXBXthcrXMqTyjtvkJK28V';
final result = UniversalAddressDetector.detectAddress(address);
expect(result.isValid, true);
expect(result.detectedCurrency, CryptoCurrency.trx);
expect(result.detectedWalletType, WalletType.tron);
expect(result.address, address);
});
});
group('Wownero Address Detection', () {
test('detects Wownero address', () {
const address =
@@ -208,41 +184,6 @@ void main() {
});
});
group('Zano Address Detection', () {
test('detects Zano address', () {
const address =
'ZxDREZKHjUAUkRpWrHV9AjJHe12RhtoY9TjUUx2EznNNdwJK9yiHEhuQkqGYnyDXRnFC3Uehu6kLGZmiQab8fWoQ1JrLsuJ1i';
final result = UniversalAddressDetector.detectAddress(address);
expect(result.isValid, true);
expect(result.detectedCurrency, CryptoCurrency.zano);
expect(result.detectedWalletType, WalletType.zano);
expect(result.address, address);
});
test('detects Zano alias', () {
const address = '@testuser';
final result = UniversalAddressDetector.detectAddress(address);
expect(result.isValid, true);
expect(result.detectedCurrency, CryptoCurrency.zano);
expect(result.detectedWalletType, WalletType.zano);
expect(result.address, address);
});
});
group('Decred Address Detection', () {
test('detects Decred address', () {
const address = 'DsW4ZtRV1DcPCuKXVVtGnCy3AtAZHg5N8nR';
final result = UniversalAddressDetector.detectAddress(address);
expect(result.isValid, true);
expect(result.detectedCurrency, CryptoCurrency.dcr);
expect(result.detectedWalletType, WalletType.decred);
expect(result.address, address);
});
});
group('Invalid Address Detection', () {
test('handles empty input', () {
const address = '';
@@ -174,14 +174,5 @@ ltcmweb1qq0at62jjucmawxp78qutn0cqwkwahcfx7fxls0r2ma5llg5w6wyy2qe20gxa3rku2658j88
// // responses for each type of address resolution.
// });
group('unstoppableDomains', () {
test('contains expected TLDs', () {
expect(AddressResolver.unstoppableDomains, contains('crypto'));
expect(AddressResolver.unstoppableDomains, contains('eth'));
expect(AddressResolver.unstoppableDomains, contains('bitcoin'));
expect(AddressResolver.unstoppableDomains, contains('x'));
expect(AddressResolver.unstoppableDomains, contains('wallet'));
});
});
});
}
-34
View File
@@ -1,34 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:hash_wallet/main.dart';
final quickActionsStream = StreamController<Uri?>.broadcast();
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(App(quickActionsStream: quickActionsStream.stream));
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}