diff --git a/.github/workflows/dart-format-fix.yml b/.github/workflows/dart-format-fix.yml index 3964c54..3f9829a 100644 --- a/.github/workflows/dart-format-fix.yml +++ b/.github/workflows/dart-format-fix.yml @@ -63,6 +63,25 @@ jobs: flutter-version: '3.41.7' cache: true + - name: Install package dependencies + # REQUIRED before `dart format`: the formatter derives each file's + # language version from .dart_tool/package_config.json. Without it the + # formatter falls back to the pubspec lower bound (3.4) and emits the OLD + # style, while CI — which runs `pub get` first — expects the NEW one. That + # mismatch is what made this bot and CI overwrite each other forever. + # `dart format` 之前必需:格式化器依据 .dart_tool/package_config.json 决定每个 + # 文件的语言版本。缺失时会回退到 pubspec 下界(3.4)并输出旧风格,而先执行了 + # `pub get` 的 CI 期望新风格 —— 正是这个差异让 bot 与 CI 长期互相覆盖。 + run: dart pub get + + - name: Install example dependencies + working-directory: example + run: flutter pub get + + - name: Install demo backend dependencies + working-directory: server + run: dart pub get + - name: Format package run: dart format . diff --git a/example/lib/main.dart b/example/lib/main.dart index 8ae6e9e..f3cd2aa 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -49,8 +49,8 @@ class _DemoStrategy implements AuthStrategy { @override Future refresh(RefreshToken token) async => login( - const Credentials(username: _validUsername, password: _validPassword), - ); + const Credentials(username: _validUsername, password: _validPassword), + ); } /// Real HTTP backend strategy. Talks to the Dart server in `../../server`. @@ -64,14 +64,14 @@ class _HttpAuthStrategy implements AuthStrategy { res.data as Map; AuthSession _toSession(Map data) => AuthSession( - accessToken: data['accessToken'] as String, - refreshToken: RefreshToken(data['refreshToken'] as String), - expiresAt: data['expiresIn'] != null - ? DateTime.now().add(Duration(seconds: data['expiresIn'] as int)) - : null, - userId: data['userId'] as String, - displayName: data['displayName'] as String, - ); + accessToken: data['accessToken'] as String, + refreshToken: RefreshToken(data['refreshToken'] as String), + expiresAt: data['expiresIn'] != null + ? DateTime.now().add(Duration(seconds: data['expiresIn'] as int)) + : null, + userId: data['userId'] as String, + displayName: data['displayName'] as String, + ); @override Future login(Credentials credentials) async { @@ -194,10 +194,10 @@ class _DemoAppState extends State { } void _toggleBackend(bool value) => setState(() { - unawaited(_auth.dispose()); - _useBackend = value; - _init(); - }); + unawaited(_auth.dispose()); + _useBackend = value; + _init(); + }); /// Runs an auth action and swallows the rethrown error: [AuthManager] already /// surfaces it as an [AuthError] state, so there is nothing left to handle. @@ -271,50 +271,51 @@ class _DemoAppState extends State { @override Widget build(BuildContext context) => MaterialApp( - title: 'zero_auth demo', - theme: _theme(Brightness.light), - darkTheme: _theme(Brightness.dark), - home: Scaffold( - appBar: AppBar(title: const Text('zero_auth demo')), - body: SafeArea( - child: StreamBuilder( - initialData: _auth.current, - stream: _auth.state, - builder: (context, snapshot) => _DemoBody( - state: snapshot.data, - auth: _auth, - useBackend: _useBackend, - baseUrl: _baseUrl, - username: _username, - password: _password, - onToggleBackend: _toggleBackend, - onLogin: () => _invoke( - () => _auth.login( - Credentials(username: _username.text, password: _password.text), + title: 'zero_auth demo', + theme: _theme(Brightness.light), + darkTheme: _theme(Brightness.dark), + home: Scaffold( + appBar: AppBar(title: const Text('zero_auth demo')), + body: SafeArea( + child: StreamBuilder( + initialData: _auth.current, + stream: _auth.state, + builder: (context, snapshot) => _DemoBody( + state: snapshot.data, + auth: _auth, + useBackend: _useBackend, + baseUrl: _baseUrl, + username: _username, + password: _password, + onToggleBackend: _toggleBackend, + onLogin: () => _invoke( + () => _auth.login( + Credentials( + username: _username.text, password: _password.text), + ), + ), + onRefresh: () => _invoke(() => _auth.refresh()), + onLogout: () => _invoke(() => _auth.logout()), + onCallMe: () => _callMe(context), + onExpireNow: () => _expireTokenNow(context), + onExpireSoon: () => _expireTokenSoon(context, 10), + onResetDebug: () => _resetDebug(context), ), ), - onRefresh: () => _invoke(() => _auth.refresh()), - onLogout: () => _invoke(() => _auth.logout()), - onCallMe: () => _callMe(context), - onExpireNow: () => _expireTokenNow(context), - onExpireSoon: () => _expireTokenSoon(context, 10), - onResetDebug: () => _resetDebug(context), ), ), - ), - ), - ); + ); /// One seed colour drives the whole palette; widgets read shades from the /// theme instead of hardcoding colors. static ThemeData _theme(Brightness brightness) => ThemeData( - useMaterial3: true, - brightness: brightness, - colorScheme: ColorScheme.fromSeed( - seedColor: const Color(0xFF00695C), - brightness: brightness, - ), - ); + useMaterial3: true, + brightness: brightness, + colorScheme: ColorScheme.fromSeed( + seedColor: const Color(0xFF00695C), + brightness: brightness, + ), + ); } /// The scrollable demo surface. Adapts to the viewport: a full-width column on @@ -578,16 +579,17 @@ class _Badge extends StatelessWidget { @override Widget build(BuildContext context) => Container( - padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), - decoration: BoxDecoration( - color: color, - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - child: Text( - label, - style: Theme.of(context).textTheme.labelMedium?.copyWith(color: onColor), - ), - ); + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), + decoration: BoxDecoration( + color: color, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Text( + label, + style: + Theme.of(context).textTheme.labelMedium?.copyWith(color: onColor), + ), + ); } /// The complete access token, wrapped over as many lines as it needs. @@ -679,13 +681,13 @@ class _InfoRow extends StatelessWidget { @override Widget build(BuildContext context) => _LabeledRow( - label: label, - child: Text( - value, - style: Theme.of(context).textTheme.bodyMedium, - overflow: TextOverflow.ellipsis, - ), - ); + label: label, + child: Text( + value, + style: Theme.of(context).textTheme.bodyMedium, + overflow: TextOverflow.ellipsis, + ), + ); } /// Failure surface. Uses the theme's error container rather than literal red, @@ -751,15 +753,15 @@ class _BackendCard extends StatelessWidget { @override Widget build(BuildContext context) => Card( - child: SwitchListTile.adaptive( - value: useBackend, - onChanged: onChanged, - title: const Text('Live backend'), - subtitle: Text(useBackend ? baseUrl : 'offline double, no server'), - secondary: const Icon(Icons.cloud_outlined), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - ), - ); + child: SwitchListTile.adaptive( + value: useBackend, + onChanged: onChanged, + title: const Text('Live backend'), + subtitle: Text(useBackend ? baseUrl : 'offline double, no server'), + secondary: const Icon(Icons.cloud_outlined), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + ), + ); } /// Sign-in form. The password can be revealed, and submitting from the keyboard diff --git a/example/lib/secure_token_store.dart b/example/lib/secure_token_store.dart index 5d8b5d9..e04e9c5 100644 --- a/example/lib/secure_token_store.dart +++ b/example/lib/secure_token_store.dart @@ -7,7 +7,7 @@ import 'package:zero_auth/zero_auth.dart'; /// is required. Swap for your own codec as needed. final class SecureTokenStore implements TokenStore { SecureTokenStore([FlutterSecureStorage? storage]) - : _storage = storage ?? const FlutterSecureStorage(); + : _storage = storage ?? const FlutterSecureStorage(); final FlutterSecureStorage _storage; diff --git a/lib/src/auth_manager.dart b/lib/src/auth_manager.dart index 3239867..8e0e66c 100644 --- a/lib/src/auth_manager.dart +++ b/lib/src/auth_manager.dart @@ -123,17 +123,17 @@ final class AuthManager implements AuthTokenSource { Duration? clockSkew, this.preserveSessionDetails = true, this.onStateChanged, - }) : tokenStore = tokenStore ?? InMemoryTokenStore(), - _autoRefreshAhead = autoRefreshAhead, - _autoRefreshRetryDelay = - autoRefreshRetryDelay ?? const Duration(seconds: 30), - _autoRefreshMaxRetries = autoRefreshMaxRetries ?? 3, - _autoRefreshMinInterval = - autoRefreshMinInterval ?? const Duration(seconds: 5), - refreshFailurePolicy = - refreshFailurePolicy ?? defaultRefreshFailurePolicy, - clock = clock ?? _systemClock, - clockSkew = clockSkew ?? const Duration(seconds: 30); + }) : tokenStore = tokenStore ?? InMemoryTokenStore(), + _autoRefreshAhead = autoRefreshAhead, + _autoRefreshRetryDelay = + autoRefreshRetryDelay ?? const Duration(seconds: 30), + _autoRefreshMaxRetries = autoRefreshMaxRetries ?? 3, + _autoRefreshMinInterval = + autoRefreshMinInterval ?? const Duration(seconds: 5), + refreshFailurePolicy = + refreshFailurePolicy ?? defaultRefreshFailurePolicy, + clock = clock ?? _systemClock, + clockSkew = clockSkew ?? const Duration(seconds: 30); final Duration? _autoRefreshAhead; @@ -220,10 +220,10 @@ final class AuthManager implements AuthTokenSource { /// 当前活动会话;未认证时为 `null`。在 [Authenticated] 与 [Refreshing] 下均可用 /// (续期中会话依然有效),但 [LoggingOut] 下为空。 AuthSession? get currentSession => switch (_state) { - Authenticated(:final session) => session, - Refreshing(:final session) => session, - _ => null, - }; + Authenticated(:final session) => session, + Refreshing(:final session) => session, + _ => null, + }; @override String? get accessToken => currentSession?.accessToken; @@ -647,11 +647,11 @@ final class AuthManager implements AuthTokenSource { /// being discarded during [LoggingOut]. /// 驱动进行中操作或已建立认证的会话,包含在 [LoggingOut] 期间正被丢弃的那个。 AuthSession? get _activeSession => switch (_state) { - Authenticated(:final session) => session, - Refreshing(:final session) => session, - LoggingOut(:final session) => session, - _ => null, - }; + Authenticated(:final session) => session, + Refreshing(:final session) => session, + LoggingOut(:final session) => session, + _ => null, + }; bool _isCurrent(int epoch) => !_disposed && epoch == _epoch; diff --git a/lib/src/auth_manager_group.dart b/lib/src/auth_manager_group.dart index 5fc76dd..0e95eb6 100644 --- a/lib/src/auth_manager_group.dart +++ b/lib/src/auth_manager_group.dart @@ -60,8 +60,8 @@ final class AuthManagerGroup implements AuthTokenSource { this.clockSkew, this.preserveSessionDetails = true, this.onStateChanged, - }) : _strategyFactory = strategyFactory, - _storeFactory = storeFactory; + }) : _strategyFactory = strategyFactory, + _storeFactory = storeFactory; /// Called once per account. Returning the same instance for every account is /// fine — and typical — since a strategy usually just talks to one backend. @@ -79,8 +79,7 @@ final class AuthManagerGroup implements AuthTokenSource { String accountId, AuthStrategy strategy, TokenStore store, - )? - managerFactory; + )? managerFactory; /// Forwarded to every manager this group creates — see [AuthManager.new]. /// 转发给分组创建的每个管理器 —— 参见 [AuthManager.new]。 @@ -157,9 +156,8 @@ final class AuthManagerGroup implements AuthTokenSource { clock: clock, clockSkew: clockSkew, preserveSessionDetails: preserveSessionDetails, - onStateChanged: observer == null - ? null - : (state) => observer(accountId, state), + onStateChanged: + observer == null ? null : (state) => observer(accountId, state), ); } diff --git a/lib/src/auth_session.dart b/lib/src/auth_session.dart index 12398ce..2b24958 100644 --- a/lib/src/auth_session.dart +++ b/lib/src/auth_session.dart @@ -94,14 +94,15 @@ final class AuthSession { String? userId, String? displayName, Map? claims, - }) => AuthSession( - accessToken: accessToken ?? this.accessToken, - refreshToken: refreshToken ?? this.refreshToken, - expiresAt: expiresAt ?? this.expiresAt, - userId: userId ?? this.userId, - displayName: displayName ?? this.displayName, - claims: claims ?? this.claims, - ); + }) => + AuthSession( + accessToken: accessToken ?? this.accessToken, + refreshToken: refreshToken ?? this.refreshToken, + expiresAt: expiresAt ?? this.expiresAt, + userId: userId ?? this.userId, + displayName: displayName ?? this.displayName, + claims: claims ?? this.claims, + ); /// How long until the access token expires, or `null` when there is no expiry. /// 距离访问令牌过期还有多久;无过期时间时为 `null`。 @@ -132,28 +133,28 @@ final class AuthSession { /// [claims] 会被原样写入,因此只能包含 JSON 安全的值(String、num、bool、null、 /// List、Map)。放入 `DateTime` 或自定义对象会让存储层的 `jsonEncode` 抛错。 Map toJson() => { - 'accessToken': accessToken, - if (refreshToken != null) 'refreshToken': refreshToken!.value, - if (expiresAt != null) 'expiresAt': expiresAt!.toIso8601String(), - if (userId != null) 'userId': userId, - if (displayName != null) 'displayName': displayName, - if (claims != null) 'claims': claims, - }; + 'accessToken': accessToken, + if (refreshToken != null) 'refreshToken': refreshToken!.value, + if (expiresAt != null) 'expiresAt': expiresAt!.toIso8601String(), + if (userId != null) 'userId': userId, + if (displayName != null) 'displayName': displayName, + if (claims != null) 'claims': claims, + }; /// Deserialize from a map produced by [toJson]. /// 从 [toJson] 生成的映射反序列化。 factory AuthSession.fromJson(Map json) => AuthSession( - accessToken: json['accessToken'] as String, - refreshToken: json['refreshToken'] == null - ? null - : RefreshToken(json['refreshToken'] as String), - expiresAt: json['expiresAt'] == null - ? null - : DateTime.parse(json['expiresAt'] as String), - userId: json['userId'] as String?, - displayName: json['displayName'] as String?, - claims: (json['claims'] as Map?)?.cast(), - ); + accessToken: json['accessToken'] as String, + refreshToken: json['refreshToken'] == null + ? null + : RefreshToken(json['refreshToken'] as String), + expiresAt: json['expiresAt'] == null + ? null + : DateTime.parse(json['expiresAt'] as String), + userId: json['userId'] as String?, + displayName: json['displayName'] as String?, + claims: (json['claims'] as Map?)?.cast(), + ); /// Deserialize from a map produced by [toJson], or return `null` when the map /// does not describe a valid session. @@ -195,9 +196,8 @@ final class AuthSession { return AuthSession( accessToken: accessToken, - refreshToken: refreshToken == null - ? null - : RefreshToken(refreshToken as String), + refreshToken: + refreshToken == null ? null : RefreshToken(refreshToken as String), expiresAt: expiresAt, userId: userId as String?, displayName: displayName as String?, @@ -217,13 +217,13 @@ final class AuthSession { @override int get hashCode => Object.hash( - accessToken, - refreshToken, - expiresAt, - userId, - displayName, - _claimsHash(claims), - ); + accessToken, + refreshToken, + expiresAt, + userId, + displayName, + _claimsHash(claims), + ); /// Claims participate in equality so a session whose *only* change is in /// `claims` still counts as new — otherwise a state emission could be diff --git a/lib/src/auth_state.dart b/lib/src/auth_state.dart index 37bc4e5..e9e9c3a 100644 --- a/lib/src/auth_state.dart +++ b/lib/src/auth_state.dart @@ -33,11 +33,11 @@ sealed class AuthState { /// [Authenticated]、[Refreshing] 与 [LoggingOut] 都携带会话;当界面只需要会话时, /// 请优先使用此属性,而不是对三个子类分别做模式匹配。 AuthSession? get session => switch (this) { - Authenticated(:final session) => session, - Refreshing(:final session) => session, - LoggingOut(:final session) => session, - _ => null, - }; + Authenticated(:final session) => session, + Refreshing(:final session) => session, + LoggingOut(:final session) => session, + _ => null, + }; } /// No active session. diff --git a/lib/src/exceptions.dart b/lib/src/exceptions.dart index 96aae2a..5ea929a 100644 --- a/lib/src/exceptions.dart +++ b/lib/src/exceptions.dart @@ -15,12 +15,12 @@ class AuthException extends AppException { final AuthFail fail; AuthException.fromFail(this.fail) - : super(fail.message, code: fail.code, cause: fail.cause); + : super(fail.message, code: fail.code, cause: fail.cause); /// Convenience constructor for manager-internal failures. /// 供管理器内部失败使用的便捷构造。 AuthException(String message, {String? code, Object? cause}) - : this.fromFail(AuthFail(message, code: code, cause: cause)); + : this.fromFail(AuthFail(message, code: code, cause: cause)); } /// Credentials were rejected by the backend (wrong password, unknown user…). @@ -38,7 +38,7 @@ final class InvalidCredentialsException extends AuthException { /// 授权已不可用:会话 / 刷新令牌 / 访问令牌已过期或被吊销,只能重新登录。 final class SessionExpiredException extends AuthException { SessionExpiredException({String message = 'Session expired', Object? cause}) - : super(message, code: 'session_expired', cause: cause); + : super(message, code: 'session_expired', cause: cause); } /// An operation that requires an active session was called with none. diff --git a/server/lib/src/auth/auth_service.dart b/server/lib/src/auth/auth_service.dart index e3c9850..7069daf 100644 --- a/server/lib/src/auth/auth_service.dart +++ b/server/lib/src/auth/auth_service.dart @@ -156,15 +156,15 @@ final class AuthService { } AuthSuccess _issue(UserRecord user, [String? refreshToken]) => AuthSuccess( - user: user, - accessToken: tokens.sign( - subject: user.id, - displayName: user.displayName, - type: TokenType.access, - ttl: accessTtl, - tokenId: tokens.newId(), - ), - refreshToken: refreshToken ?? refreshTokens.issue(user), - expiresIn: accessTtl.inSeconds, - ); + user: user, + accessToken: tokens.sign( + subject: user.id, + displayName: user.displayName, + type: TokenType.access, + ttl: accessTtl, + tokenId: tokens.newId(), + ), + refreshToken: refreshToken ?? refreshTokens.issue(user), + expiresIn: accessTtl.inSeconds, + ); } diff --git a/server/lib/src/auth/token_service.dart b/server/lib/src/auth/token_service.dart index ea8d519..99e67fd 100644 --- a/server/lib/src/auth/token_service.dart +++ b/server/lib/src/auth/token_service.dart @@ -33,9 +33,9 @@ final class TokenService { required String secret, Duration clockSkew = const Duration(seconds: 1), Random? random, - }) : _secret = utf8.encode(secret), - _clockSkew = clockSkew, - _random = random ?? Random.secure(); + }) : _secret = utf8.encode(secret), + _clockSkew = clockSkew, + _random = random ?? Random.secure(); final List _secret; final Duration _clockSkew; diff --git a/server/lib/src/auth/user_store.dart b/server/lib/src/auth/user_store.dart index 098acff..c2d5083 100644 --- a/server/lib/src/auth/user_store.dart +++ b/server/lib/src/auth/user_store.dart @@ -13,7 +13,7 @@ final class UserRecord { /// in the backend has to change. final class UserStore { UserStore({Map? credentials}) - : _credentials = credentials ?? _defaultCredentials; + : _credentials = credentials ?? _defaultCredentials; static const _defaultCredentials = {'user': 'user'}; diff --git a/server/lib/src/config.dart b/server/lib/src/config.dart index 76e9a1b..e6b1e62 100644 --- a/server/lib/src/config.dart +++ b/server/lib/src/config.dart @@ -32,8 +32,9 @@ final class ServerConfig { static const _defaultSecret = 'demo-secret-change-me'; static Duration _durationFromEnv(String key, int defaultSeconds) => Duration( - seconds: int.tryParse(Platform.environment[key] ?? '') ?? defaultSeconds, - ); + seconds: + int.tryParse(Platform.environment[key] ?? '') ?? defaultSeconds, + ); final String host; final int port; diff --git a/server/lib/src/logging/logger.dart b/server/lib/src/logging/logger.dart index 582b142..21a3f9f 100644 --- a/server/lib/src/logging/logger.dart +++ b/server/lib/src/logging/logger.dart @@ -9,8 +9,8 @@ enum LogLevel { debug, info, warn, error } /// `LOG_LEVEL=debug` to also see per-route debug lines. final class Logger { Logger({LogLevel minimum = LogLevel.info, Stdout? output}) - : _minimum = minimum, - _out = output ?? stdout; + : _minimum = minimum, + _out = output ?? stdout; final LogLevel _minimum; final Stdout _out; diff --git a/test/auth_manager_hardening_test.dart b/test/auth_manager_hardening_test.dart index a215dd1..4a79436 100644 --- a/test/auth_manager_hardening_test.dart +++ b/test/auth_manager_hardening_test.dart @@ -93,15 +93,16 @@ void main() { String token = 'access', String name = 'user@demo', bool expired = false, - }) => AuthSession( - accessToken: token, - refreshToken: const RefreshToken('refresh'), - expiresAt: expired - ? now.subtract(const Duration(minutes: 1)) - : now.add(const Duration(minutes: 5)), - userId: 'user', - displayName: name, - ); + }) => + AuthSession( + accessToken: token, + refreshToken: const RefreshToken('refresh'), + expiresAt: expired + ? now.subtract(const Duration(minutes: 1)) + : now.add(const Duration(minutes: 5)), + userId: 'user', + displayName: name, + ); group('restore hardening', () { test('a logout during restore does not resurrect the session', () async { diff --git a/test/auth_manager_lifecycle_test.dart b/test/auth_manager_lifecycle_test.dart index 3dd6e9d..693bbc9 100644 --- a/test/auth_manager_lifecycle_test.dart +++ b/test/auth_manager_lifecycle_test.dart @@ -37,14 +37,15 @@ void main() { bool expired = false, bool withRefresh = true, Duration ttl = const Duration(hours: 1), - }) => AuthSession( - accessToken: accessToken, - refreshToken: withRefresh ? const RefreshToken('refresh') : null, - expiresAt: expired - ? fixedNow.subtract(const Duration(minutes: 5)) - : fixedNow.add(ttl), - userId: 'u1', - ); + }) => + AuthSession( + accessToken: accessToken, + refreshToken: withRefresh ? const RefreshToken('refresh') : null, + expiresAt: expired + ? fixedNow.subtract(const Duration(minutes: 5)) + : fixedNow.add(ttl), + userId: 'u1', + ); /// Lets pending microtasks settle so stream emissions become observable. /// 让挂起的微任务执行完,使状态流的新值可被观察。 @@ -349,21 +350,24 @@ void main() { group('AuthManager — proactive refresh', () { test('failures never leak an unhandled async error', () async { final errors = []; - final zoneRun = runZonedGuarded>(() async { - final strategy = FakeAuthStrategy( - session: buildSession(ttl: Duration.zero), - )..refreshError = SessionExpiredException(); - final manager = AuthManager( - strategy: strategy, - tokenStore: InMemoryTokenStore(), - autoRefreshAhead: const Duration(minutes: 5), - clock: () => fixedNow, - ); - await manager.login(credentials); - await pump(); - expect(manager.current, const Unauthenticated()); - await manager.dispose(); - }, (error, stack) => errors.add(error)); + final zoneRun = runZonedGuarded>( + () async { + final strategy = FakeAuthStrategy( + session: buildSession(ttl: Duration.zero), + )..refreshError = SessionExpiredException(); + final manager = AuthManager( + strategy: strategy, + tokenStore: InMemoryTokenStore(), + autoRefreshAhead: const Duration(minutes: 5), + clock: () => fixedNow, + ); + await manager.login(credentials); + await pump(); + expect(manager.current, const Unauthenticated()); + await manager.dispose(); + }, + (error, stack) => errors.add(error), + ); await (zoneRun ?? Future.value()); expect(errors, isEmpty); }); diff --git a/test/auth_manager_test.dart b/test/auth_manager_test.dart index dcfb62d..ccf12cc 100644 --- a/test/auth_manager_test.dart +++ b/test/auth_manager_test.dart @@ -13,11 +13,11 @@ final class ExtendingAuthStrategy implements AuthStrategy { int refreshCount = 0; AuthSession _session(DateTime expiresAt) => AuthSession( - accessToken: 'access', - refreshToken: const RefreshToken('refresh'), - expiresAt: expiresAt, - userId: 'u1', - ); + accessToken: 'access', + refreshToken: const RefreshToken('refresh'), + expiresAt: expiresAt, + userId: 'u1', + ); @override Future login(Credentials credentials) async => diff --git a/test/auth_manager_v1_test.dart b/test/auth_manager_v1_test.dart index 52fc380..397700b 100644 --- a/test/auth_manager_v1_test.dart +++ b/test/auth_manager_v1_test.dart @@ -44,8 +44,8 @@ final class _FailingStore implements TokenStore { this.failSaveAfter = 0, this.failClear = false, AuthSession? initial, - }) : value = initial, - _allowedSaves = failSaveAfter; + }) : value = initial, + _allowedSaves = failSaveAfter; final int failSaveAfter; final bool failClear; @@ -167,10 +167,10 @@ final class _ShortTtlStrategy implements AuthStrategy { } AuthSession _issue() => AuthSession( - accessToken: 'access-$refreshCount', - refreshToken: const RefreshToken('refresh'), - expiresAt: DateTime.now().add(const Duration(milliseconds: 300)), - ); + accessToken: 'access-$refreshCount', + refreshToken: const RefreshToken('refresh'), + expiresAt: DateTime.now().add(const Duration(milliseconds: 300)), + ); } /// A strategy that keeps handing out sessions that are already due, to prove the @@ -197,10 +197,10 @@ final class _ShortLivedStrategy implements AuthStrategy { } AuthSession _issued() => AuthSession( - accessToken: 'access-$refreshCount', - refreshToken: const RefreshToken('refresh'), - expiresAt: clock().add(const Duration(seconds: 1)), - ); + accessToken: 'access-$refreshCount', + refreshToken: const RefreshToken('refresh'), + expiresAt: clock().add(const Duration(seconds: 1)), + ); } final class _StaticTokenSource implements AuthTokenSource { @@ -219,14 +219,15 @@ AuthSession _session( String? userId, String? displayName, Map? claims, -}) => AuthSession( - accessToken: token, - refreshToken: const RefreshToken('refresh'), - expiresAt: expiresAt, - userId: userId, - displayName: displayName, - claims: claims, -); +}) => + AuthSession( + accessToken: token, + refreshToken: const RefreshToken('refresh'), + expiresAt: expiresAt, + userId: userId, + displayName: displayName, + claims: claims, + ); const _credentials = Credentials(username: 'user', password: 'user'); @@ -422,7 +423,8 @@ void main() { expect(manager.currentSession?.accessToken, 'login-1'); }); - test('a new refresh does not join one started in a previous epoch', () async { + test('a new refresh does not join one started in a previous epoch', + () async { final gate = Completer(); final strategy = _GatedStrategy( gate: gate, diff --git a/test/fake_strategy.dart b/test/fake_strategy.dart index 14267b0..93e6db8 100644 --- a/test/fake_strategy.dart +++ b/test/fake_strategy.dart @@ -17,14 +17,14 @@ final class FakeAuthStrategy implements AuthStrategy { bool logoutCalled = false; FakeAuthStrategy({AuthSession? session}) - : nextSession = session ?? _default(); + : nextSession = session ?? _default(); static AuthSession _default() => const AuthSession( - accessToken: 'access', - refreshToken: RefreshToken('refresh'), - userId: 'u1', - displayName: 'User', - ); + accessToken: 'access', + refreshToken: RefreshToken('refresh'), + userId: 'u1', + displayName: 'User', + ); @override Future login(Credentials credentials) async {