From a13d3d57d3c5c04c5b9632269b41e5e9cd87739f Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Mon, 7 Jul 2025 12:39:45 +0530 Subject: [PATCH] tests: add auth fields widget tests(coverage 98.4%) --- .../auth/api_key_auth_fields.dart | 9 +- .../auth/basic_auth_fields.dart | 8 +- .../auth/bearer_auth_fields.dart | 20 +- .../auth/digest_auth_fields.dart | 186 ++++---- .../request_pane/request_auth.dart | 2 - .../auth/api_key_auth_fields_test.dart | 294 +++++++++++++ .../auth/basic_auth_fields_test.dart | 231 ++++++++++ .../auth/bearer_auth_fields_test.dart | 237 ++++++++++ .../auth/digest_auth_fields_test.dart | 405 ++++++++++++++++++ .../auth/jwt_auth_fields_test.dart | 372 ++++++++++++++++ 10 files changed, 1665 insertions(+), 99 deletions(-) create mode 100644 test/screens/common_widgets/auth/api_key_auth_fields_test.dart create mode 100644 test/screens/common_widgets/auth/basic_auth_fields_test.dart create mode 100644 test/screens/common_widgets/auth/bearer_auth_fields_test.dart create mode 100644 test/screens/common_widgets/auth/digest_auth_fields_test.dart create mode 100644 test/screens/common_widgets/auth/jwt_auth_fields_test.dart diff --git a/lib/screens/common_widgets/auth/api_key_auth_fields.dart b/lib/screens/common_widgets/auth/api_key_auth_fields.dart index a32e3d42..8fd272c9 100644 --- a/lib/screens/common_widgets/auth/api_key_auth_fields.dart +++ b/lib/screens/common_widgets/auth/api_key_auth_fields.dart @@ -92,7 +92,14 @@ class _ApiKeyAuthFieldsState extends State { name: _nameController.text.trim(), location: _addKeyTo, ), - ), + ) ?? AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: _keyController.text.trim(), + name: _nameController.text.trim(), + location: _addKeyTo, + ), + ) ); } } diff --git a/lib/screens/common_widgets/auth/basic_auth_fields.dart b/lib/screens/common_widgets/auth/basic_auth_fields.dart index 6cb6ad1a..91f30336 100644 --- a/lib/screens/common_widgets/auth/basic_auth_fields.dart +++ b/lib/screens/common_widgets/auth/basic_auth_fields.dart @@ -61,7 +61,13 @@ class BasicAuthFields extends StatelessWidget { username: usernameController.text.trim(), password: passwordController.text.trim(), ), - ), + ) ?? AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: usernameController.text.trim(), + password: passwordController.text.trim(), + ), + ) ); } } diff --git a/lib/screens/common_widgets/auth/bearer_auth_fields.dart b/lib/screens/common_widgets/auth/bearer_auth_fields.dart index 322ab52b..0569831f 100644 --- a/lib/screens/common_widgets/auth/bearer_auth_fields.dart +++ b/lib/screens/common_widgets/auth/bearer_auth_fields.dart @@ -40,13 +40,17 @@ class _BearerAuthFieldsState extends State { } void _updateBearerAuth() { - widget.updateAuth( - widget.authData?.copyWith( - type: APIAuthType.bearer, - bearer: AuthBearerModel( - token: _tokenController.text.trim(), - ), - ), - ); + widget.updateAuth(widget.authData?.copyWith( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: _tokenController.text.trim(), + ), + ) ?? + AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: _tokenController.text.trim(), + ), + )); } } diff --git a/lib/screens/common_widgets/auth/digest_auth_fields.dart b/lib/screens/common_widgets/auth/digest_auth_fields.dart index 7fc7850b..859f3cec 100644 --- a/lib/screens/common_widgets/auth/digest_auth_fields.dart +++ b/lib/screens/common_widgets/auth/digest_auth_fields.dart @@ -43,97 +43,109 @@ class _DigestAuthFieldsState extends State { @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - AuthTextField( - readOnly: widget.readOnly, - controller: _usernameController, - hintText: "Username", - onChanged: (_) => _updateDigestAuth(), - ), - const SizedBox(height: 16), - AuthTextField( - readOnly: widget.readOnly, - controller: _passwordController, - hintText: "Password", - isObscureText: true, - onChanged: (_) => _updateDigestAuth(), - ), - const SizedBox(height: 16), - AuthTextField( - readOnly: widget.readOnly, - controller: _realmController, - hintText: "Realm", - onChanged: (_) => _updateDigestAuth(), - ), - const SizedBox(height: 16), - AuthTextField( - readOnly: widget.readOnly, - controller: _nonceController, - hintText: "Nonce", - onChanged: (_) => _updateDigestAuth(), - ), - const SizedBox(height: 16), - Text( - "Algorithm", - style: TextStyle( - fontWeight: FontWeight.normal, - fontSize: 14, + return SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AuthTextField( + readOnly: widget.readOnly, + controller: _usernameController, + hintText: "Username", + onChanged: (_) => _updateDigestAuth(), ), - ), - SizedBox(height: 4), - ADPopupMenu( - value: _algorithmController.trim(), - values: const [ - ('MD5', 'MD5'), - ('MD5-sess', 'MD5-sess'), - ('SHA-256', 'SHA-256'), - ('SHA-256-sess', 'SHA-256-sess'), - ], - tooltip: "this algorithm will be used to produce the digest", - isOutlined: true, - onChanged: (String? newLocation) { - if (newLocation != null) { - setState(() { - _algorithmController = newLocation; - }); - _updateDigestAuth(); - } - }, - ), - const SizedBox(height: 16), - AuthTextField( - readOnly: widget.readOnly, - controller: _qopController, - hintText: "QOP (e.g. auth)", - onChanged: (_) => _updateDigestAuth(), - ), - const SizedBox(height: 16), - AuthTextField( - readOnly: widget.readOnly, - controller: _opaqueController, - hintText: "Opaque", - onChanged: (_) => _updateDigestAuth(), - ), - ], + const SizedBox(height: 12), + AuthTextField( + readOnly: widget.readOnly, + controller: _passwordController, + hintText: "Password", + isObscureText: true, + onChanged: (_) => _updateDigestAuth(), + ), + const SizedBox(height: 12), + AuthTextField( + readOnly: widget.readOnly, + controller: _realmController, + hintText: "Realm", + onChanged: (_) => _updateDigestAuth(), + ), + const SizedBox(height: 12), + AuthTextField( + readOnly: widget.readOnly, + controller: _nonceController, + hintText: "Nonce", + onChanged: (_) => _updateDigestAuth(), + ), + const SizedBox(height: 12), + Text( + "Algorithm", + style: TextStyle( + fontWeight: FontWeight.normal, + fontSize: 14, + ), + ), + SizedBox(height: 4), + ADPopupMenu( + value: _algorithmController.trim(), + values: const [ + ('MD5', 'MD5'), + ('MD5-sess', 'MD5-sess'), + ('SHA-256', 'SHA-256'), + ('SHA-256-sess', 'SHA-256-sess'), + ], + tooltip: "this algorithm will be used to produce the digest", + isOutlined: true, + onChanged: (String? newLocation) { + if (newLocation != null) { + setState(() { + _algorithmController = newLocation; + }); + _updateDigestAuth(); + } + }, + ), + const SizedBox(height: 12), + AuthTextField( + readOnly: widget.readOnly, + controller: _qopController, + hintText: "QOP (e.g. auth)", + onChanged: (_) => _updateDigestAuth(), + ), + const SizedBox(height: 12), + AuthTextField( + readOnly: widget.readOnly, + controller: _opaqueController, + hintText: "Opaque", + onChanged: (_) => _updateDigestAuth(), + ), + ], + ), ); } void _updateDigestAuth() { - widget.updateAuth( - widget.authData?.copyWith( - type: APIAuthType.digest, - digest: AuthDigestModel( - username: _usernameController.text.trim(), - password: _passwordController.text.trim(), - realm: _realmController.text.trim(), - nonce: _nonceController.text.trim(), - algorithm: _algorithmController.trim(), - qop: _qopController.text.trim(), - opaque: _opaqueController.text.trim(), - ), - ), - ); + widget.updateAuth(widget.authData?.copyWith( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: _usernameController.text.trim(), + password: _passwordController.text.trim(), + realm: _realmController.text.trim(), + nonce: _nonceController.text.trim(), + algorithm: _algorithmController.trim(), + qop: _qopController.text.trim(), + opaque: _opaqueController.text.trim(), + ), + ) ?? + AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: _usernameController.text.trim(), + password: _passwordController.text.trim(), + realm: _realmController.text.trim(), + nonce: _nonceController.text.trim(), + algorithm: _algorithmController.trim(), + qop: _qopController.text.trim(), + opaque: _opaqueController.text.trim(), + ), + )); } } diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart index 971d98dd..fd3e6362 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_auth.dart @@ -92,8 +92,6 @@ class EditAuthType extends ConsumerWidget { ), ); } - - // ...existing code... Widget _buildAuthFields( BuildContext context, WidgetRef ref, diff --git a/test/screens/common_widgets/auth/api_key_auth_fields_test.dart b/test/screens/common_widgets/auth/api_key_auth_fields_test.dart new file mode 100644 index 00000000..19f80080 --- /dev/null +++ b/test/screens/common_widgets/auth/api_key_auth_fields_test.dart @@ -0,0 +1,294 @@ +import 'package:apidash/screens/common_widgets/auth/api_key_auth_fields.dart'; +import 'package:apidash/screens/common_widgets/auth_textfield.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('ApiKeyAuthFields Widget Tests', () { + late AuthModel? mockAuthData; + late Function(AuthModel?) mockUpdateAuth; + late List capturedAuthUpdates; + + setUp(() { + capturedAuthUpdates = []; + mockUpdateAuth = (AuthModel? authModel) { + capturedAuthUpdates.add(authModel); + }; + }); + + testWidgets('renders with default values when authData is null', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add to'), findsOneWidget); + expect(find.byType(ADPopupMenu), findsOneWidget); + expect(find.byType(AuthTextField), findsNWidgets(2)); + expect(find.text('Header'), findsOneWidget); + }); + + testWidgets( + 'updates auth data when authData is null and API key value is changed', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: null, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the key field (second AuthTextField) + final keyField = find.byType(AuthTextField).last; + await tester.tap(keyField); + await tester.enterText(keyField, 'new-api-key'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.apikey?.key, 'new-api-key'); + }); + + testWidgets('renders with existing API key auth data', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'test-api-key', + name: 'X-API-Key', + location: 'header', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add to'), findsOneWidget); + expect(find.text('Header'), findsOneWidget); + expect(find.byType(AuthTextField), findsNWidgets(2)); + }); + + testWidgets('renders with query params location', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'test-api-key', + name: 'api_key', + location: 'query', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add to'), findsOneWidget); + expect(find.text('Query Params'), findsOneWidget); + }); + + testWidgets('updates auth data when location dropdown changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'test-key', + name: 'X-API-Key', + location: 'header', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find and tap the dropdown + await tester.tap(find.byType(ADPopupMenu)); + await tester.pumpAndSettle(); + + // Select Query Params option + await tester.tap(find.text('Query Params').last); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.apikey?.location, 'query'); + }); + + testWidgets('updates auth data when API key name changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'test-key', + name: 'X-API-Key', + location: 'header', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the name field (first AuthTextField) + final nameField = find.byType(AuthTextField).first; + await tester.tap(nameField); + await tester.enterText(nameField, 'Authorization'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.apikey?.name, 'Authorization'); + }); + + testWidgets('updates auth data when API key value changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'old-key', + name: 'X-API-Key', + location: 'header', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the key field (second AuthTextField) + final keyField = find.byType(AuthTextField).last; + await tester.tap(keyField); + await tester.enterText(keyField, 'new-api-key'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.apikey?.key, 'new-api-key'); + }); + + testWidgets('respects readOnly property', (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: 'test-key', + name: 'X-API-Key', + location: 'header', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + readOnly: true, + ), + ), + ), + ); + + // Verify that AuthTextField widgets are rendered + expect(find.byType(AuthTextField), findsNWidgets(2)); + + // The readOnly property should be passed to AuthTextField widgets + // This is verified by the widget structure itself + }); + + testWidgets('displays correct hint texts', (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add to'), findsOneWidget); + // Check for the existence of the auth text fields + expect(find.byType(AuthTextField), findsNWidgets(2)); + }); + testWidgets('initializes with correct default values', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ApiKeyAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Default location should be header + expect(find.text('Header'), findsOneWidget); + + // Default name should be 'x-api-key' in the text field + expect(find.text('x-api-key'), findsOneWidget); + }); + }); +} diff --git a/test/screens/common_widgets/auth/basic_auth_fields_test.dart b/test/screens/common_widgets/auth/basic_auth_fields_test.dart new file mode 100644 index 00000000..a2ca01bb --- /dev/null +++ b/test/screens/common_widgets/auth/basic_auth_fields_test.dart @@ -0,0 +1,231 @@ +import 'package:apidash/screens/common_widgets/auth/basic_auth_fields.dart'; +import 'package:apidash/screens/common_widgets/auth_textfield.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('BasicAuthFields Widget Tests', () { + late AuthModel? mockAuthData; + late Function(AuthModel?) mockUpdateAuth; + late List capturedAuthUpdates; + + setUp(() { + capturedAuthUpdates = []; + mockUpdateAuth = (AuthModel? authModel) { + capturedAuthUpdates.add(authModel); + }; + }); + + testWidgets('renders with default values when authData is null', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(2)); + expect(find.text('Username'), findsNWidgets(2)); + expect(find.text('Password'), findsNWidgets(2)); + }); + + testWidgets('renders with existing basic auth data', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'testuser', + password: 'testpass', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(2)); + expect(find.text('Username'), findsExactly(2)); + expect(find.text('Password'), findsExactly(2)); + }); + + testWidgets('updates auth data when username changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'olduser', + password: 'password', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the username field (first AuthTextField) + final usernameField = find.byType(AuthTextField).first; + await tester.tap(usernameField); + await tester.enterText(usernameField, 'newuser'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.basic?.username, 'newuser'); + expect(lastUpdate?.type, APIAuthType.basic); + }); + + testWidgets('updates auth data when password changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'user', + password: 'oldpass', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the password field (second AuthTextField) + final passwordField = find.byType(AuthTextField).last; + await tester.tap(passwordField); + await tester.enterText(passwordField, 'newpass'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.basic?.password, 'newpass'); + expect(lastUpdate?.type, APIAuthType.basic); + }); + + testWidgets('respects readOnly property', (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: 'user', + password: 'pass', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + readOnly: true, + ), + ), + ), + ); + + // Verify that AuthTextField widgets are rendered + expect(find.byType(AuthTextField), findsNWidgets(2)); + + // The readOnly property should be passed to AuthTextField widgets + // This is verified by the widget structure itself + }); + + testWidgets('displays correct hint texts', (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(2)); + }); + + testWidgets('handles empty auth data gracefully', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.basic, + basic: AuthBasicAuthModel( + username: '', + password: '', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(2)); + }); + + testWidgets('creates proper AuthModel on field changes', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BasicAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Enter username + final usernameField = find.byType(AuthTextField).first; + await tester.tap(usernameField); + await tester.enterText(usernameField, 'testuser'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called with correct structure + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.type, APIAuthType.basic); + expect(lastUpdate?.basic?.username, 'testuser'); + }); + }); +} diff --git a/test/screens/common_widgets/auth/bearer_auth_fields_test.dart b/test/screens/common_widgets/auth/bearer_auth_fields_test.dart new file mode 100644 index 00000000..51ef74a9 --- /dev/null +++ b/test/screens/common_widgets/auth/bearer_auth_fields_test.dart @@ -0,0 +1,237 @@ +import 'package:apidash/screens/common_widgets/auth/bearer_auth_fields.dart'; +import 'package:apidash/screens/common_widgets/auth_textfield.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('BearerAuthFields Widget Tests', () { + late AuthModel? mockAuthData; + late Function(AuthModel?) mockUpdateAuth; + late List capturedAuthUpdates; + + setUp(() { + capturedAuthUpdates = []; + mockUpdateAuth = (AuthModel? authModel) { + capturedAuthUpdates.add(authModel); + }; + }); + + testWidgets('renders with default values when authData is null', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsOneWidget); + expect(find.text('Token'), findsNWidgets(2)); + }); + + testWidgets('renders with existing bearer auth data', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: 'test-bearer-token', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsOneWidget); + expect(find.text('Token'), findsNWidgets(2)); + }); + + testWidgets('updates auth data when token changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: 'old-token', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the token field + final tokenField = find.byType(AuthTextField); + await tester.tap(tokenField); + await tester.enterText(tokenField, 'new-bearer-token'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.bearer?.token, 'new-bearer-token'); + expect(lastUpdate?.type, APIAuthType.bearer); + }); + + testWidgets('respects readOnly property', (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: 'test-token', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + readOnly: true, + ), + ), + ), + ); + + // Verify that AuthTextField widget is rendered + expect(find.byType(AuthTextField), findsOneWidget); + + // The readOnly property should be passed to AuthTextField widget + // This is verified by the widget structure itself + }); + + testWidgets('displays correct hint text', (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsOneWidget); + }); + + testWidgets('handles empty auth data gracefully', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel( + token: '', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsOneWidget); + }); + + testWidgets('creates proper AuthModel on token change', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Enter token + final tokenField = find.byType(AuthTextField); + await tester.tap(tokenField); + await tester.enterText(tokenField, 'test-bearer-token'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called with correct structure + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.type, APIAuthType.bearer); + expect(lastUpdate?.bearer?.token, 'test-bearer-token'); + }); + + testWidgets('initializes with correct default token value', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // The token field should be empty initially + expect(find.byType(AuthTextField), findsOneWidget); + }); + + testWidgets('trims whitespace from token input', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: BearerAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Enter token with whitespace + final tokenField = find.byType(AuthTextField); + await tester.tap(tokenField); + await tester.enterText(tokenField, ' test-token '); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called with trimmed token + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.bearer?.token, 'test-token'); + }); + }); +} diff --git a/test/screens/common_widgets/auth/digest_auth_fields_test.dart b/test/screens/common_widgets/auth/digest_auth_fields_test.dart new file mode 100644 index 00000000..0c43e9ad --- /dev/null +++ b/test/screens/common_widgets/auth/digest_auth_fields_test.dart @@ -0,0 +1,405 @@ +import 'package:apidash/screens/common_widgets/auth/digest_auth_fields.dart'; +import 'package:apidash/screens/common_widgets/auth_textfield.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/widgets/widgets.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('DigestAuthFields Widget Tests', () { + late AuthModel? mockAuthData; + late Function(AuthModel?) mockUpdateAuth; + late List capturedAuthUpdates; + + setUp(() { + capturedAuthUpdates = []; + mockUpdateAuth = (AuthModel? authModel) { + capturedAuthUpdates.add(authModel); + }; + }); + + testWidgets('renders with default values when authData is null', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(6)); + expect(find.byType(ADPopupMenu), findsOneWidget); + // Check for field labels (each AuthTextField creates a Text widget for label) + expect(find.text('Username'), findsNWidgets(2)); + expect(find.text('Password'), findsNWidgets(2)); + expect(find.text('Realm'), findsNWidgets(2)); + expect(find.text('Nonce'), findsNWidgets(2)); + expect(find.text('Algorithm'), findsOneWidget); + expect(find.text('QOP (e.g. auth)'), findsNWidgets(2)); + expect(find.text('Opaque'), findsNWidgets(2)); + }); + + testWidgets('renders with existing digest auth data', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'testuser', + password: 'testpass', + realm: 'testrealm', + nonce: 'testnonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'testopaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(6)); + expect(find.byType(ADPopupMenu), findsOneWidget); + expect(find.text('MD5'), findsOneWidget); + }); + + testWidgets('updates auth data when username changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'olduser', + password: 'pass', + realm: 'realm', + nonce: 'nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'opaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the username field (first AuthTextField) + final usernameField = find.byType(AuthTextField).first; + await tester.tap(usernameField); + await tester.enterText(usernameField, 'newuser'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.digest?.username, 'newuser'); + expect(lastUpdate?.type, APIAuthType.digest); + }); + + testWidgets('updates auth data when password changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'user', + password: 'oldpass', + realm: 'realm', + nonce: 'nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'opaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the password field (second AuthTextField) + final passwordField = find.byType(AuthTextField).at(1); + await tester.tap(passwordField); + await tester.enterText(passwordField, 'newpass'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.digest?.password, 'newpass'); + expect(lastUpdate?.type, APIAuthType.digest); + }); + + testWidgets('updates auth data when algorithm dropdown changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'user', + password: 'pass', + realm: 'realm', + nonce: 'nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'opaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find and tap the algorithm dropdown + await tester.tap(find.byType(ADPopupMenu)); + await tester.pumpAndSettle(); + + // Select SHA-256 option + await tester.tap(find.text('SHA-256').last); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.digest?.algorithm, 'SHA-256'); + }); + + testWidgets('updates auth data when realm changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'user', + password: 'pass', + realm: 'oldrealm', + nonce: 'nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'opaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the realm field (third AuthTextField) + final realmField = find.byType(AuthTextField).at(2); + await tester.tap(realmField); + await tester.enterText(realmField, 'newrealm'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.digest?.realm, 'newrealm'); + }); + + testWidgets('respects readOnly property', (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.digest, + digest: AuthDigestModel( + username: 'user', + password: 'pass', + realm: 'realm', + nonce: 'nonce', + algorithm: 'MD5', + qop: 'auth', + opaque: 'opaque', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + readOnly: true, + ), + ), + ), + ); + + final usernameFieldFinder = find.byType(AuthTextField).first; + + // Try to enter text + await tester.enterText(usernameFieldFinder, 'testuser'); + await tester.pumpAndSettle(); + + // Ensure updateAuth was not called + expect(capturedAuthUpdates, isEmpty); + + // Check the field still shows original value + final textField = tester.widget(usernameFieldFinder); + expect(textField.controller.text, equals('user')); + }); + + testWidgets('displays correct hint texts', (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.byType(AuthTextField), findsNWidgets(6)); + expect(find.byType(ADPopupMenu), findsOneWidget); + expect(find.text('Algorithm'), findsOneWidget); + }); + + testWidgets('initializes with correct default values', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Default algorithm should be MD5 + expect(find.text('MD5'), findsOneWidget); + + // Default QOP should be 'auth' - but this is in the TextFormField value, not visible text + // We need to check the controller value instead + expect(find.byType(AuthTextField), findsNWidgets(6)); + }); + + testWidgets('creates proper AuthModel on field changes', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Enter username + final usernameField = find.byType(AuthTextField).first; + await tester.tap(usernameField); + await tester.enterText(usernameField, 'testuser'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called with correct structure + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.type, APIAuthType.digest); + expect(lastUpdate?.digest?.username, 'testuser'); + expect(lastUpdate?.digest?.algorithm, 'MD5'); + }); + + testWidgets('handles all algorithm options correctly', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Test each algorithm option + final algorithms = ['MD5', 'MD5-sess', 'SHA-256', 'SHA-256-sess']; + + for (final algorithm in algorithms) { + // Tap the dropdown + await tester.tap(find.byType(ADPopupMenu)); + await tester.pumpAndSettle(); + + // Select the algorithm + await tester.tap(find.text(algorithm).last); + await tester.pumpAndSettle(); + + // Verify the selection + expect(find.text(algorithm), findsOneWidget); + } + }); + + testWidgets('trims whitespace from all field inputs', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: DigestAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Enter username with whitespace + final usernameField = find.byType(AuthTextField).first; + await tester.tap(usernameField); + await tester.enterText(usernameField, ' testuser '); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called with trimmed values + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.digest?.username, 'testuser'); + }); + }); +} diff --git a/test/screens/common_widgets/auth/jwt_auth_fields_test.dart b/test/screens/common_widgets/auth/jwt_auth_fields_test.dart new file mode 100644 index 00000000..7856c43b --- /dev/null +++ b/test/screens/common_widgets/auth/jwt_auth_fields_test.dart @@ -0,0 +1,372 @@ +import 'package:apidash/screens/common_widgets/auth/jwt_auth_fields.dart'; +import 'package:apidash/screens/common_widgets/auth_textfield.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('JwtAuthFields Widget Tests', () { + late AuthModel? mockAuthData; + late Function(AuthModel?) mockUpdateAuth; + late List capturedAuthUpdates; + + setUp(() { + capturedAuthUpdates = []; + mockUpdateAuth = (AuthModel? authModel) { + capturedAuthUpdates.add(authModel); + }; + }); + + testWidgets('renders with default values when authData is null', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add JWT token to'), findsOneWidget); + expect(find.text('Algorithm'), findsOneWidget); + expect(find.text('Payload (JSON format)'), findsOneWidget); + expect(find.byType(ADPopupMenu), findsNWidgets(2)); + expect(find.text('Request Header'), findsOneWidget); + expect(find.text('HS256'), findsOneWidget); + }); + + testWidgets('renders with existing JWT auth data', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'test-secret', + privateKey: '', + payload: '{"sub": "1234567890"}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Add JWT token to'), findsOneWidget); + expect(find.text('Algorithm'), findsOneWidget); + expect(find.text('Request Header'), findsOneWidget); + expect(find.text('HS256'), findsOneWidget); + }); + + testWidgets('shows secret field for HMAC algorithms', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'test-secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Secret key'), findsExactly(2)); + expect(find.text('Secret is Base64 encoded'), findsOneWidget); + expect(find.byType(AuthTextField), findsOneWidget); + expect(find.byType(CheckboxListTile), findsOneWidget); + }); + + testWidgets('shows private key field for RSA algorithms', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: '', + privateKey: 'test-private-key', + payload: '{}', + addTokenTo: 'header', + algorithm: 'RS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + expect(find.text('Private Key (PEM Format)'), findsOneWidget); + expect(find.text('Secret key'), findsNothing); + expect(find.byType(TextField), findsNWidgets(2)); // Private key + payload + }); + + testWidgets('updates auth data when add token to dropdown changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find and tap the first dropdown (add token to) + await tester.tap(find.byType(ADPopupMenu).first); + await tester.pumpAndSettle(); + + // Select Query Parameters option + await tester.tap(find.text('Query Parameters').last); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.jwt?.addTokenTo, 'query'); + }); + + testWidgets('updates auth data when algorithm dropdown changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find and tap the second dropdown (algorithm) + await tester.tap(find.byType(ADPopupMenu).last); + await tester.pumpAndSettle(); + + // Select RS256 option + await tester.tap(find.text('RS256').last); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.jwt?.algorithm, 'RS256'); + }); + + testWidgets('updates auth data when secret changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'old-secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the secret field + final secretField = find.byType(AuthTextField).first; + await tester.tap(secretField); + await tester.enterText(secretField, 'new-secret'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.jwt?.secret, 'new-secret'); + }); + + testWidgets('updates auth data when payload changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find the payload field (TextField) + final payloadField = find.byType(TextField).last; + await tester.tap(payloadField); + await tester.enterText(payloadField, '{"sub": "1234567890"}'); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.jwt?.payload, '{"sub": "1234567890"}'); + }); + + testWidgets('updates auth data when Base64 checkbox changes', + (WidgetTester tester) async { + mockAuthData = const AuthModel( + type: APIAuthType.jwt, + jwt: AuthJwtModel( + secret: 'secret', + privateKey: '', + payload: '{}', + addTokenTo: 'header', + algorithm: 'HS256', + isSecretBase64Encoded: false, + headerPrefix: 'Bearer', + queryParamKey: 'token', + header: 'Authorization', + ), + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Find and tap the checkbox + await tester.tap(find.byType(CheckboxListTile)); + await tester.pumpAndSettle(); + + // Verify that updateAuth was called + expect(capturedAuthUpdates.length, greaterThan(0)); + final lastUpdate = capturedAuthUpdates.last; + expect(lastUpdate?.jwt?.isSecretBase64Encoded, true); + }); + + testWidgets('initializes with correct default values', + (WidgetTester tester) async { + mockAuthData = null; + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: JwtAuthFields( + authData: mockAuthData, + updateAuth: mockUpdateAuth, + ), + ), + ), + ); + + // Default token location should be header + expect(find.text('Request Header'), findsOneWidget); + + // Default algorithm should be HS256 + expect(find.text('HS256'), findsOneWidget); + + // Default Base64 encoded should be false + expect(find.byType(CheckboxListTile), findsOneWidget); + }); + }); +}