diene_e2e 1.0.0 copy "diene_e2e: ^1.0.0" to clipboard
diene_e2e: ^1.0.0 copied to clipboard

L-dart family version train and consumer test harness: bundles member test helpers and ships journey drivers, stub servers, and the shared C0 app-handoff fixture.

example/diene_e2e_example.dart

// Drive the C0 §7 deferred-login (app-handoff) journey end to end against the
// shared stub, then assert the outcome — the whole reason this package exists.
//
// TWO IMPORTS, and the split is the point. `diene_e2e.dart` is the VERSION TRAIN:
// depending on this one package gives you the coherent runtime API of all seven
// L-dart members, so a consumer pins one version instead of seven.
// `test_helper.dart` is the TEST HARNESS: the stub server, the journey drivers,
// the shared app-handoff fixture, plain-throw assertions, and every member's own
// test helper re-exported. Importing the harness never drags a test framework
// into a production dependency graph — everything in it is fakes, builders and
// plain-throw assertions.
//
// `print` is the right output for a runnable example, so the production-code lint
// is waived for this file only.
// ignore_for_file: avoid_print
import 'dart:convert';
import 'dart:io';

import 'package:diene_e2e/diene_e2e.dart';
import 'package:diene_e2e/test_helper.dart';

Future<void> main() async {
  // 1. Stand up the shared app-handoff stub. There is exactly ONE stub server in
  //    the family and it is defined WITH the C0 contract — this package consumes
  //    it rather than building a second one, so a consumer's journey is tested
  //    against the same behaviour the contract specifies.
  final StubServer server = await StubServer.start();
  final AppHandoffStub handoff = AppHandoffStub(
    problemTypeUri: 'https://api.example.com/AppHandoffExpired',
  )..addUser(const AppHandoffUser(sub: 'user-1', primaryEmail: 'a@b.com'));
  handoff.mount(server);

  // 2. Mint a carrier the way the store install flow would. `mintingUser` is set
  //    explicitly because the real server reads that identity from the
  //    authenticated session; the stub refuses to mint without it rather than
  //    attributing a token to nobody.
  handoff.mintingUser = const AppHandoffUser(
    sub: 'user-1',
    primaryEmail: 'a@b.com',
  );

  // 3. Drive the mobile client's half of the flow: parse the carrier, redeem the
  //    nonce, fall back to interactive login on any failure.
  final DeferredLoginJourney journey = DeferredLoginJourney(
    baseUrl: server.baseUrl,
    platform: 'android',
  );

  // The carrier is BUILT with the package's own codec rather than hand-written.
  // A hand-written referrer string is how you get a silent interactive-fallback
  // that looks like a passing journey: `parseAppHandoffInstallReferrer` requires
  // exactly one correctly-named `app_handoff` field, so a near-miss parses to
  // null and the redeem never happens.
  // Minted over REAL HTTP against the mounted route, the same call the backend
  // would receive, so the example exercises the path a consumer's journey takes.
  final HttpClient client = HttpClient();
  final HttpClientRequest mintRequest = await client.postUrl(
    Uri.parse('${server.baseUrl}${handoff.mountPath}'),
  );
  mintRequest.write('{}');
  final HttpClientResponse mintResponse = await mintRequest.close();
  final String mintBody = await utf8.decoder.bind(mintResponse).join();
  final String nonce = AppHandoffMint.fromJson(
    (jsonDecode(mintBody) as Map<Object?, Object?>).map(
      (Object? k, Object? v) => MapEntry<String, Object?>(k.toString(), v),
    ),
  ).nonce;

  final String referrer = buildAppHandoffInstallReferrer(
    nonce,
    extraFields: <String, String>{
      'utm_source': 'play',
      'utm_campaign': 'launch',
    },
  );

  final Journey flow = Journey('deferred login', <JourneyStep>[
    JourneyStep('carrier redeems into a one-time token', () async {
      final DeferredLoginResult result = await journey.redeemCarrier(referrer);
      // Assert the SPECIFIC branch. Accepting either outcome would make this
      // step unfalsifiable — an interactive fallback caused by a broken carrier
      // would read as success.
      return result.outcome == DeferredLoginJourneyOutcome.redeemed &&
          result.redeem != null;
    }),
    JourneyStep('a replayed nonce is refused', () async {
      // Single-use is a contract property, so the second redeem of the same
      // nonce must fall back rather than mint again.
      final DeferredLoginResult replay = await journey.redeemNonce(nonce);
      return replay.outcome == DeferredLoginJourneyOutcome.interactiveFallback;
    }),
  ]);

  final JourneyResult outcome = await flow.run();

  // 4. Assert with the harness's plain-throw helpers. They throw
  //    JourneyAssertionError rather than depending on `package:test`, so they
  //    work inside whatever runner the consumer already has.
  expectJourneyOk(outcome);
  expectTrue(server.requests.isNotEmpty, 'the journey must reach the server');
  print(
    'journey ${flow.name}: ${outcome.steps.length} steps, ok=${outcome.ok}',
  );

  // 5. The version train in one line: `Problem` comes from diene_problems and
  //    `Result` from diene_result, both reached through this single package.
  //    Every fallible member API returns a Result and never throws.
  const Result<int> resolved = Ok<int>(1);
  print('bundled Result resolves to ${expectOk(resolved)}');

  client.close(force: true);
  await server.close();
  journey.close();
}
0
likes
160
points
33
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

L-dart family version train and consumer test harness: bundles member test helpers and ships journey drivers, stub servers, and the shared C0 app-handoff fixture.

Repository (GitHub)
View/report issues

Topics

#testing #e2e #test-helper #fixtures #atomicloud

License

MIT (license)

Dependencies

diene_api_engine, diene_auth_engine, diene_config, diene_core_utils, diene_interfaces, diene_problems, diene_result, flutter

More

Packages that depend on diene_e2e