mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:07:18 +01:00
Some checks are pending
data_transfer_flutter tests / test (push) Waiting to run
dependency freshness / dependencies on newest stable (push) Waiting to run
file length / file-length (push) Waiting to run
md-naming / markdown naming convention (push) Waiting to run
palette drift / palette (push) Waiting to run
repo-contract / repo contract (push) Waiting to run
shared gate tests / gate unit tests (push) Waiting to run
sync_settings_ui tests / test (push) Waiting to run
Decide by AGP library marker files instead of dex strings: play-services-base/basement reference GoogleSignInAccount without linking play-services-auth. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWVQE5pYiASpRGgA3Kg
196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
"""check_no_gms_signin.sh: no app may link Google's non-free sign-in stack.
|
|
|
|
Each test writes a throwaway Flutter-shaped project (pubspec.lock, optional
|
|
android/ gradle files) or a fake APK built with zipfile, runs the real
|
|
script, and asserts on the exit code and the message.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import subprocess
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
SCRIPT = Path(__file__).resolve().parent.parent / "check_no_gms_signin.sh"
|
|
|
|
# The package keys of kuhy's real kartka lock (web-only GIS button).
|
|
KARTKA_KEYS = (
|
|
"google_account_token",
|
|
"google_identity_services_web",
|
|
"google_sign_in_platform_interface",
|
|
"google_sign_in_web",
|
|
)
|
|
|
|
|
|
def _lock(root: Path, *packages: str) -> Path:
|
|
"""Write a pubspec.lock under `root` with the given package keys."""
|
|
root.mkdir(parents=True, exist_ok=True)
|
|
body = ["# Generated by pub", "packages:"]
|
|
for name in ("collection", *packages):
|
|
body += [
|
|
f" {name}:",
|
|
' dependency: "direct main"',
|
|
" description:",
|
|
f" name: {name}",
|
|
" source: hosted",
|
|
' version: "1.0.0"',
|
|
]
|
|
body += ["sdks:", ' dart: ">=3.0.0 <4.0.0"', ""]
|
|
lock = root / "pubspec.lock"
|
|
lock.write_text("\n".join(body), encoding="utf-8")
|
|
return lock
|
|
|
|
|
|
def _run(*args: str | Path) -> subprocess.CompletedProcess[str]:
|
|
return subprocess.run(
|
|
["bash", str(SCRIPT), *map(str, args)],
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
def test_clean_lock_passes(tmp_path: Path) -> None:
|
|
lock = _lock(tmp_path, "google_account_token", "http")
|
|
assert _run(lock).returncode == 0
|
|
|
|
|
|
def test_google_sign_in_android_fails_and_names_the_fix(tmp_path: Path) -> None:
|
|
lock = _lock(tmp_path, "google_sign_in_android")
|
|
proc = _run(lock)
|
|
assert proc.returncode == 1
|
|
assert str(lock) in proc.stderr
|
|
assert "google_sign_in_android" in proc.stderr
|
|
assert "google_account_token-v0.1.0" in proc.stderr
|
|
assert "google_account_token/README.md" in proc.stderr
|
|
|
|
|
|
def test_app_facing_google_sign_in_fails(tmp_path: Path) -> None:
|
|
proc = _run(_lock(tmp_path, "google_sign_in"))
|
|
assert proc.returncode == 1
|
|
assert "google_sign_in " in proc.stderr or "google_sign_in\n" in proc.stderr
|
|
|
|
|
|
def test_kartka_style_web_only_passes(tmp_path: Path) -> None:
|
|
assert _run(_lock(tmp_path, *KARTKA_KEYS)).returncode == 0
|
|
|
|
|
|
def test_web_pieces_fail_once_android_is_linked(tmp_path: Path) -> None:
|
|
proc = _run(_lock(tmp_path, *KARTKA_KEYS, "google_sign_in_android"))
|
|
assert proc.returncode == 1
|
|
assert "google_sign_in_web" in proc.stderr
|
|
|
|
|
|
def test_gradle_play_services_auth_fails(tmp_path: Path) -> None:
|
|
lock = _lock(tmp_path)
|
|
gradle = tmp_path / "android" / "app" / "build.gradle.kts"
|
|
gradle.parent.mkdir(parents=True)
|
|
gradle.write_text(
|
|
'dependencies {\n implementation("com.google.gms:google-services:4")\n'
|
|
' implementation("com.google.android.gms:play-services-auth:21.3.0")\n}\n',
|
|
encoding="utf-8",
|
|
)
|
|
proc = _run(lock)
|
|
assert proc.returncode == 1
|
|
assert str(gradle) in proc.stderr
|
|
assert "play-services-auth" in proc.stderr
|
|
|
|
|
|
def test_versions_toml_googleid_fails(tmp_path: Path) -> None:
|
|
lock = _lock(tmp_path)
|
|
toml = tmp_path / "android" / "gradle" / "libs.versions.toml"
|
|
toml.parent.mkdir(parents=True)
|
|
toml.write_text(
|
|
'googleid = "com.google.android.libraries.identity.googleid:googleid:1.1"\n',
|
|
encoding="utf-8",
|
|
)
|
|
assert _run(lock).returncode == 1
|
|
|
|
|
|
def test_firebase_google_services_plugin_passes(tmp_path: Path) -> None:
|
|
lock = _lock(tmp_path)
|
|
gradle = tmp_path / "android" / "build.gradle"
|
|
gradle.parent.mkdir(parents=True)
|
|
gradle.write_text(
|
|
"// play-services-auth was removed 2026-11-02\n"
|
|
"classpath 'com.google.gms:google-services:4.4.2'\n",
|
|
encoding="utf-8",
|
|
)
|
|
assert _run(lock).returncode == 0
|
|
|
|
|
|
def test_directory_mode_finds_nested_locks_and_prunes_build(tmp_path: Path) -> None:
|
|
_lock(tmp_path / "app", "google_account_token")
|
|
_lock(tmp_path / "app" / "build" / "x", "google_sign_in_android")
|
|
_lock(tmp_path / ".dart_tool" / "y", "google_sign_in_android")
|
|
# Agent worktrees are other checkouts, judged in their own repo.
|
|
_lock(tmp_path / ".claude" / "worktrees" / "z", "google_sign_in_android")
|
|
assert _run(tmp_path).returncode == 0
|
|
_lock(tmp_path / "other", "google_sign_in_ios")
|
|
proc = _run(tmp_path)
|
|
assert proc.returncode == 1
|
|
assert "google_sign_in_ios" in proc.stderr
|
|
|
|
|
|
def test_missing_explicit_file_is_a_usage_error(tmp_path: Path) -> None:
|
|
assert _run(tmp_path / "nope.lock").returncode == 2
|
|
|
|
|
|
CREDENTIALS_MARKER = "META-INF/androidx.credentials_credentials-play-services-auth.version"
|
|
DEX_REF = b"\x00Lcom/google/android/gms/auth/api/signin/GoogleSignInAccount;\x00" * 2
|
|
|
|
|
|
def _apk(path: Path, dex: bytes = b"", markers: tuple[str, ...] = ()) -> Path:
|
|
"""Fake APK: dex strings plus library marker files AGP copies in."""
|
|
with zipfile.ZipFile(path, "w") as apk:
|
|
apk.writestr("AndroidManifest.xml", b"\x03\x00")
|
|
apk.writestr("classes.dex", b"dex\n035\x00" + dex)
|
|
apk.writestr("classes2.dex", b"dex\n035\x00Lkotlin/Unit;\x00")
|
|
for name in markers:
|
|
apk.writestr(name, b"version=1")
|
|
return path
|
|
|
|
|
|
def test_apk_with_only_dex_references_passes(tmp_path: Path) -> None:
|
|
"""play-services-base references GoogleSignInAccount without linking auth."""
|
|
markers = (
|
|
"play-services-base.properties",
|
|
"play-services-basement.properties",
|
|
"play-services-tasks.properties",
|
|
"play-services-fido.properties",
|
|
)
|
|
proc = _run("--apk", _apk(tmp_path / "ok.apk", DEX_REF, markers))
|
|
assert proc.returncode == 0, proc.stderr
|
|
|
|
|
|
def test_apk_without_markers_passes(tmp_path: Path) -> None:
|
|
assert _run("--apk", _apk(tmp_path / "clean.apk")).returncode == 0
|
|
|
|
|
|
def test_apk_banned_markers_fail_and_are_named(tmp_path: Path) -> None:
|
|
banned = (
|
|
"play-services-auth.properties",
|
|
"googleid.properties",
|
|
"play-services-auth-base.properties",
|
|
"play-services-auth-api-phone.properties",
|
|
"play-services-auth-blockstore.properties",
|
|
"play-services-identity-credentials.properties",
|
|
CREDENTIALS_MARKER,
|
|
)
|
|
for marker in banned:
|
|
apk = _apk(tmp_path / "bad.apk", DEX_REF, ("play-services-base.properties", marker))
|
|
proc = _run("--apk", apk)
|
|
assert proc.returncode == 1, marker
|
|
assert marker in proc.stderr
|
|
assert "google_account_token" in proc.stderr
|
|
|
|
|
|
def test_apk_nested_marker_name_is_not_a_root_marker(tmp_path: Path) -> None:
|
|
"""Only APK-root files count; a same-named file in a subdir is not AGP's."""
|
|
apk = _apk(tmp_path / "nested.apk", markers=("assets/play-services-auth.properties",))
|
|
assert _run("--apk", apk).returncode == 0
|
|
|
|
|
|
def test_apk_missing_file_is_a_usage_error(tmp_path: Path) -> None:
|
|
assert _run("--apk", tmp_path / "nope.apk").returncode == 2
|