From 12886177cc81f3e4d12ac3bf7cdc4b8794b3ce0d Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 8 Oct 2026 10:34:13 +0300 Subject: [PATCH 1/2] test(e2e): transaction controls, destructive guard and cancel on close --- .../e2e_transaction_guard_test.dart | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart diff --git a/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart b/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart new file mode 100644 index 00000000..d3e7929d --- /dev/null +++ b/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart @@ -0,0 +1,114 @@ +import 'dart:async'; + +import 'package:flutter/material.dart' as material; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../support/fake_sql_execution_delegate.dart'; +import '../helpers/e2e_app_harness.dart'; +import '../helpers/e2e_sql_workspace_helper.dart'; + +const _timeout = Timeout(Duration(seconds: 60)); + +/// A database that really tracks `BEGIN` / `COMMIT` / `ROLLBACK`. +class _TxDelegate extends FakeSqlExecutionDelegate { + var open = false; + final commands = []; + + @override + bool get supportsTransactions => true; + + @override + Future runTransactionCommand(String command, + {Duration? timeout}) async { + commands.add(command); + open = command == 'BEGIN'; + } + + @override + Future checkTransactionOpen() async => open; +} + +void main() { + final app = E2eAppHarness(prefix: 'querya_e2e_tx_'); + late E2eSqlWorkspace ws; + setUpAll(() async { + await app.setUpAll(); + ws = await E2eSqlWorkspace.create(); + }); + tearDownAll(app.tearDownAll); + + Future tapAndSettle(WidgetTester tester, String label) async { + await tester.tap(find.text(label)); + await E2eSqlWorkspace.settle(tester); + } + + testWidgets('Begin opens a transaction, Rollback and Commit close it', + timeout: _timeout, (tester) async { + final delegate = _TxDelegate(); + await ws.pump(tester, delegate); + await E2eSqlWorkspace.settle(tester); + expect(find.text('Transaction: none'), findsOneWidget); + + await tapAndSettle(tester, 'Begin'); + expect(find.text('Transaction: open'), findsOneWidget); + expect(find.text('OK: BEGIN'), findsWidgets); + + await tapAndSettle(tester, 'Rollback'); + expect(find.text('Transaction: none'), findsOneWidget); + + await tapAndSettle(tester, 'Begin'); + await tapAndSettle(tester, 'Commit'); + expect(find.text('Transaction: none'), findsOneWidget); + + expect(delegate.commands, ['BEGIN', 'ROLLBACK', 'BEGIN', 'COMMIT']); + }); + + testWidgets('a database without transactions shows no transaction controls', + timeout: _timeout, (tester) async { + await ws.pump(tester, FakeSqlExecutionDelegate()); + + expect(find.text('Begin'), findsNothing); + expect(find.text('Commit'), findsNothing); + expect(find.text('Rollback'), findsNothing); + }); + + testWidgets('a destructive statement waits for confirmation', + timeout: _timeout, (tester) async { + final delegate = FakeSqlExecutionDelegate(); + await ws.pump(tester, delegate, initialSql: 'DELETE FROM users'); + + await E2eSqlWorkspace.ctrl(tester, LogicalKeyboardKey.enter); + await E2eSqlWorkspace.settle(tester); + expect(find.text('Execute Destructive Statement'), findsOneWidget); + expect(delegate.executed, isEmpty); + + await tester.tap(find.text('Cancel')); + await E2eSqlWorkspace.settle(tester); + expect(delegate.executed, isEmpty); + + await E2eSqlWorkspace.ctrl(tester, LogicalKeyboardKey.enter); + await E2eSqlWorkspace.settle(tester); + await tester.tap(find.text('Execute Destructive Statement')); + await E2eSqlWorkspace.settle(tester); + expect(delegate.executed, ['DELETE FROM users']); + }); + + testWidgets('closing the workspace while a query runs cancels it', + timeout: _timeout, (tester) async { + final delegate = FakeSqlExecutionDelegate()..gate = Completer(); + await ws.pump(tester, delegate, initialSql: 'SELECT pg_sleep(60)'); + + await E2eSqlWorkspace.ctrl(tester, LogicalKeyboardKey.enter); + await E2eSqlWorkspace.settle(tester); + expect(delegate.executed, ['SELECT pg_sleep(60)']); + expect(delegate.cancelCount, 0); + + await tester.pumpWidget(const material.SizedBox()); + await tester.pump(); + + expect(delegate.cancelCount, 1); + delegate.gate!.complete(); + await E2eSqlWorkspace.settle(tester); + }); +} From ad44a05f2c6fdc2bb74affcc0a210f2dd88e511f Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Thu, 8 Oct 2026 10:37:24 +0300 Subject: [PATCH 2/2] test(e2e): acknowledge the destructive warning and expect the unknown tx label --- .../02_sql_workspaces/e2e_transaction_guard_test.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart b/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart index d3e7929d..41915479 100644 --- a/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart +++ b/test/e2e/02_sql_workspaces/e2e_transaction_guard_test.dart @@ -48,7 +48,8 @@ void main() { final delegate = _TxDelegate(); await ws.pump(tester, delegate); await E2eSqlWorkspace.settle(tester); - expect(find.text('Transaction: none'), findsOneWidget); + // Unknown until the first command refreshes the state. + expect(find.text('Transaction: —'), findsOneWidget); await tapAndSettle(tester, 'Begin'); expect(find.text('Transaction: open'), findsOneWidget); @@ -89,6 +90,13 @@ void main() { await E2eSqlWorkspace.ctrl(tester, LogicalKeyboardKey.enter); await E2eSqlWorkspace.settle(tester); + // The button stays disabled until the warning is acknowledged. + await tester.tap(find.text('Execute Destructive Statement'), + warnIfMissed: false); + await E2eSqlWorkspace.settle(tester); + expect(delegate.executed, isEmpty); + await tester.tap(find.byType(material.Checkbox)); + await tester.pump(); await tester.tap(find.text('Execute Destructive Statement')); await E2eSqlWorkspace.settle(tester); expect(delegate.executed, ['DELETE FROM users']);