Files
pleno-vue/tests/node/verify-android-target-sdk.test.mjs
T
openhands d2f440840b ci(mobile): enforce Android targetSdk 36 in App Store readiness check (TRU-141)
Google Play Store requires new apps and updates to target API 36
(Android 16, "Baklava") as of Aug 2026. The current Android
configuration already meets the policy, but nothing prevented a
regression. Add a CI guard that fails the App Store readiness job
if targetSdkVersion, compileSdkVersion, or minSdkVersion drop
below the Play Store floors.

- scripts/mobile/verify-android-target-sdk.mjs: reads
  android/variables.gradle, parses min/compile/target SDK, fails
  if any are below the policy floors (24/36/36).
- tests/node/verify-android-target-sdk.test.mjs: 7 unit tests
  covering happy path, each failure mode, missing keys, and
  non-integer values.
- .github/workflows/app-store-readiness.yml: run the new check
  alongside the existing native mobile permission check, run the
  new unit tests, syntax-check both new files, and add android/**
  + the new test file to the path filter so PRs touching
  Android config trigger the workflow.

Refs TRU-141.
2026-08-17 14:00:29 +00:00

134 lines
3.6 KiB
JavaScript

// verify-android-target-sdk.test.mjs
//
// Unit tests for the Android target-SDK guard. We don't read the
// real android/variables.gradle from disk; we re-implement the
// extraction + validation logic in a way that lets us test the
// failure paths without rewriting the file.
import assert from "node:assert/strict";
import { test } from "node:test";
const REQUIRED_TARGET_SDK = 36;
const REQUIRED_COMPILE_SDK = 36;
const MIN_ACCEPTABLE_MIN_SDK = 24;
const SDK_NAMES = {
24: "7.0 Nougat",
35: "15",
36: "16 (Baklava)",
};
const androidName = (v) => SDK_NAMES[v] ?? `API ${v}`;
const extract = (text, key) => {
const match = text.match(new RegExp(`^\\s*${key}\\s*=\\s*(\\d+)\\s*$`, "m"));
return match ? Number.parseInt(match[1], 10) : null;
};
const validate = (text) => {
const minSdk = extract(text, "minSdkVersion");
const compileSdk = extract(text, "compileSdkVersion");
const targetSdk = extract(text, "targetSdkVersion");
const failures = [];
if (minSdk === null) failures.push("minSdkVersion missing");
else if (minSdk < MIN_ACCEPTABLE_MIN_SDK) failures.push("minSdk too low");
if (compileSdk === null) failures.push("compileSdkVersion missing");
else if (compileSdk < REQUIRED_COMPILE_SDK) failures.push("compileSdk too low");
if (targetSdk === null) failures.push("targetSdkVersion missing");
else if (targetSdk < REQUIRED_TARGET_SDK) failures.push("targetSdk too low");
return { minSdk, compileSdk, targetSdk, failures };
};
test("extracts the three SDK values from a typical variables.gradle", () => {
const text = `
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
}
`;
const { minSdk, compileSdk, targetSdk, failures } = validate(text);
assert.equal(minSdk, 24);
assert.equal(compileSdk, 36);
assert.equal(targetSdk, 36);
assert.deepEqual(failures, []);
});
test("flags targetSdkVersion below 36 as a failure", () => {
const text = `
ext {
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 35
}
`;
const { failures } = validate(text);
assert.ok(
failures.includes("targetSdk too low"),
`expected targetSdk failure, got: ${JSON.stringify(failures)}`,
);
});
test("flags compileSdkVersion below 36 as a failure", () => {
const text = `
ext {
minSdkVersion = 24
compileSdkVersion = 35
targetSdkVersion = 36
}
`;
const { failures } = validate(text);
assert.ok(
failures.includes("compileSdk too low"),
`expected compileSdk failure, got: ${JSON.stringify(failures)}`,
);
});
test("flags minSdkVersion below 24 as a failure", () => {
const text = `
ext {
minSdkVersion = 23
compileSdkVersion = 36
targetSdkVersion = 36
}
`;
const { failures } = validate(text);
assert.ok(
failures.includes("minSdk too low"),
`expected minSdk failure, got: ${JSON.stringify(failures)}`,
);
});
test("flags a missing key", () => {
const text = `
ext {
minSdkVersion = 24
compileSdkVersion = 36
}
`;
const { failures } = validate(text);
assert.ok(
failures.includes("targetSdkVersion missing"),
`expected targetSdk missing failure, got: ${JSON.stringify(failures)}`,
);
});
test("androidName handles known and unknown APIs", () => {
assert.equal(androidName(24), "7.0 Nougat");
assert.equal(androidName(36), "16 (Baklava)");
assert.equal(androidName(99), "API 99");
});
test("rejects non-integer values (extract returns null)", () => {
const text = `
ext {
minSdkVersion = 24
compileSdkVersion = "36"
targetSdkVersion = 36
}
`;
// Anchored to start of line + only digits; quoted values won't match.
const compileSdk = extract(text, "compileSdkVersion");
assert.equal(compileSdk, null);
});