Skip to content

test(android): prototype integration tests - #14905

Draft
thomaseizinger wants to merge 6 commits into
android-service-owns-its-flowsfrom
android-integration-test-prototypes
Draft

test(android): prototype integration tests#14905
thomaseizinger wants to merge 6 commits into
android-service-owns-its-flowsfrom
android-integration-test-prototypes

Conversation

@thomaseizinger

@thomaseizinger thomaseizinger commented Aug 29, 2026

Copy link
Copy Markdown
Member

Everything worth covering on Android sits behind one call: TunnelService builds a connlib Session itself, so its event loop, its state flows and its teardown are unreachable to a test with no portal. SessionFactory is the seam that changes: the service asks for a session instead of constructing one, and the real factory is a Hilt provider that does what the service used to do inline.

With that in place, three sketches of what coverage could look like, deliberately shallow and meant to be compared rather than merged as-is:

  • Instrumented, service level. The real service on the emulator with a scripted session standing in for connlib. Covers resources reaching the flows, a disconnect clearing the token and taking the service down, a failing open leaving nothing running, and a managed token and device name reaching connlib.
  • Instrumented, screen level. The same fake, asserted on what the user sees: resources the service already holds render on a freshly launched SessionActivity, and a disconnect closes the screen.
  • Robolectric, on the JVM. Managed configuration travelling from RestrictionsManager through MainActivity into the repository, and the connect-on-start decision. No emulator, so these run in the unit-test job.

Both instrumented suites run in the connectedDebugAndroidTest job that already exists and has had nothing to run.

The service also had to stop starting connlib's telemetry client in builds stamped FIREZONE_NO_TELEMETRY, which the Kotlin side already honours: otherwise the emulator would report to Sentry on every CI run, which is exactly what that flag is set to prevent.

TunnelService still reaches connlib directly for logging and telemetry, which is why the JVM tests stay away from it; only the session is behind a seam.

claude added 6 commits August 29, 2026 05:47
Adds a seam for opening connlib sessions so the service's event loop can be
driven without a portal, and three sketches of what integration coverage could
look like: instrumented tests on the service, an instrumented test on the
session screen, and Robolectric tests for managed configuration and
connect-on-start.
The tunnel runs as a systemExempted foreground service, which the platform
refuses to start unless the app holds VPN consent; without it the first
startForeground threw and took the instrumentation process down with it.

Robolectric only dispatches a broadcast when the main looper is advanced, so
the boot test never reached BootReceiver.
A started service outlives the test that started it, so the next test could
end up driving a service wired up from an object graph that no longer exists:
its fake session factory was one no test could see. The fake now outlives the
graph and each test stops whatever the previous one left running.

Timeouts also say which wait expired and what the service was doing, since an
emulator in CI offers no other way to tell.
Stopping the service was not enough to get rid of it: its event loop was still
waiting on a fake session that never ended, so it never stopped itself. Ending
those sessions first does, and the managed configuration a test writes now
reaches a service that may predate it.
The service was surviving with started=false and one client: something held a
binding that was never released, and a bound service is destroyed for nobody.
The tests bind and unbind themselves now, which is the only way to be sure.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants