verifyOAuthAuthorizationCodeExchangeStoreConformance function

Future<void> verifyOAuthAuthorizationCodeExchangeStoreConformance(
  1. OAuthAuthorizationCodeExchangeStoreFactory createStore
)

Verifies the portable atomic exchange contract for a persistence adapter.

Durable adapters should run this function in their own test suite against an isolated database namespace. Passing these cases does not certify a backend transaction implementation; adapter authors must additionally fault the database commit itself and prove rollback.

Implementation

Future<void> verifyOAuthAuthorizationCodeExchangeStoreConformance(
  OAuthAuthorizationCodeExchangeStoreFactory createStore,
) async {
  await _case('exchange.commit', () async {
    final fixture = await _fixture(await createStore(), 'commit');
    final result = await fixture.store.commit(
      request: fixture.request,
      expectedAuthorizationId: fixture.code.authorizationId,
      preparedToken: fixture.token,
    );
    _check(
      result.status == OAuthAuthorizationCodeExchangeStatus.committed,
      'exchange did not commit',
    );
    _check(
      await fixture.store.accessTokenStore.findByToken(fixture.rawToken) !=
          null,
      'prepared token was not persisted',
    );
  });

  await _case('exchange.concurrent-one-winner', () async {
    final fixture = await _fixture(await createStore(), 'concurrent');
    final results = await Future.wait(
      List<Future<OAuthAuthorizationCodeExchangeResult>>.generate(
        16,
        (_) async => fixture.store.commit(
          request: fixture.request,
          expectedAuthorizationId: fixture.code.authorizationId,
          preparedToken: fixture.token,
        ),
      ),
    );
    _check(
      results
              .where(
                (result) =>
                    result.status ==
                    OAuthAuthorizationCodeExchangeStatus.committed,
              )
              .length ==
          1,
      'exchange had more than one winner',
    );
    _check(
      results.every(
        (result) =>
            result.status == OAuthAuthorizationCodeExchangeStatus.committed ||
            result.status ==
                OAuthAuthorizationCodeExchangeStatus.alreadyCommitted,
      ),
      'contending retry returned an unexpected result',
    );
  });

  await _case('exchange.wrong-binding-consumes', () async {
    final fixture = await _fixture(await createStore(), 'wrong-binding');
    final wrong = OAuthAuthorizationCodeExchangeRequest(
      codeHash: fixture.request.codeHash,
      clientId: fixture.request.clientId,
      redirectUri: fixture.request.redirectUri,
      codeVerifier: 'wrong-verifier',
      now: fixture.request.now,
    );
    _check(
      (await fixture.store.prepare(wrong)).status ==
          OAuthAuthorizationCodePreparationStatus.invalidGrant,
      'wrong binding was accepted',
    );
    _check(
      (await fixture.store.prepare(fixture.request)).status ==
          OAuthAuthorizationCodePreparationStatus.invalidGrant,
      'wrong binding did not consume the matching digest',
    );
  });

  await _case('exchange.authorization-id-collision', () async {
    final fixture = await _fixture(await createStore(), 'collision');
    var rejected = false;
    try {
      await fixture.store.authorizationCodeStore.create(
        _code(
          namespace: 'collision-other',
          authorizationId: fixture.code.authorizationId,
          codeHash: hashOpaqueToken('collision-other-code'),
        ),
      );
    } on StateError {
      rejected = true;
    }
    _check(rejected, 'duplicate authorization ID was accepted');
  });
}