Files
edgeever/scripts/store-delivery.test.mjs
T
tianma-if 9f09fc1042 fix(ios): require Japanese App Store What's New before review
The 1.79.0 listing includes ja, so Fastlane rejected submit_review when
release-summary.json had no Japanese notes. Fail in store-delivery and
Fastfile instead of creating a stuck draft.
2026-09-21 00:11:41 +08:00

233 lines
8.6 KiB
JavaScript

import { describe, expect, test } from "bun:test";
import { readFileSync } from "node:fs";
import { parseStoreDeliveryArgs } from "./store-delivery.mjs";
describe("store delivery command", () => {
test("uses full production delivery defaults", () => {
expect(parseStoreDeliveryArgs(["--release", "v1.7.0"])).toMatchObject({
releaseTag: "v1.7.0",
platform: "both",
androidTrack: "production",
});
});
test("accepts an explicit single platform", () => {
expect(
parseStoreDeliveryArgs(["--release", "v1.7.0", "--platform", "ios"])
.platform,
).toBe("ios");
});
test("supports recovering a Play-signed APK without re-uploading", () => {
expect(
parseStoreDeliveryArgs([
"--release",
"v1.7.0",
"--platform",
"android",
"--recover-play-apk",
]).recoverPlayApk,
).toBe(true);
});
test("supports submitting an existing iOS build without rebuilding", () => {
expect(
parseStoreDeliveryArgs([
"--release",
"v1.7.0",
"--platform",
"ios",
"--ios-build-number",
"30",
]).iosBuildNumber,
).toBe("30");
});
test("rejects malformed release tags", () => {
expect(() => parseStoreDeliveryArgs(["--release", "latest"])).toThrow(
"stable vX.Y.Z",
);
});
test("uses the pinned official EAS CLI setup in store jobs", () => {
const workflow = readFileSync(
new URL("../.github/workflows/store-delivery.yml", import.meta.url),
"utf8",
);
expect(workflow).not.toContain("bunx eas-cli");
expect(workflow.match(/uses: expo\/expo-github-action@v8/g)).toHaveLength(
1,
);
expect(workflow.match(/eas-version: 21\.4\.0/g)).toHaveLength(1);
expect(workflow.match(/packager: npm/g)).toHaveLength(1);
expect(workflow).toContain("for attempt in 1 2 3");
expect(workflow).toContain("Dependency install failed on attempt ${attempt}/3");
expect(
workflow.match(
/edgeever-bun-cache-\$\{GITHUB_RUN_ID\}-\$\{GITHUB_RUN_ATTEMPT\}/g,
),
).toHaveLength(2);
expect(workflow).not.toContain("eas build");
expect(workflow).not.toContain("--platform ios");
expect(workflow).toContain("working-directory: apps/ios");
expect(workflow).toContain(
"group: edgeever-store-delivery-${{ inputs.release_tag }}-${{ inputs.platform }}",
);
expect(workflow).toContain(".edgeever-ci/scripts/xcode-cloud-control.py");
expect(workflow).toContain("--wait-valid");
expect(workflow).not.toContain("--require-sha");
expect(workflow).not.toContain("--git-ref");
expect(workflow).toContain("git show \"$source_sha:apps/ios/Config/Version.xcconfig\"");
expect(workflow).toContain("cloud_version");
expect(workflow).toContain("grep -E '^(build_number|app_store_build_id|build_run_id|source_sha|processing_state)='");
expect(workflow).toContain('refs/tags/${RELEASE_TAG}');
expect(workflow).toContain(
"APP_STORE_BUILD_NUMBER: ${{ steps.ios_build.outputs.build_number }}",
);
expect(workflow).toContain("APP_STORE_RELEASE_NOTES_EN:");
expect(workflow).toContain("missing Japanese What's New");
expect(workflow).not.toContain(
"Pass ios_build_number; do not build from apps/mobile or EAS.",
);
expect(workflow).toContain(
"APP_STORE_CONNECT_API_ISSUER_ID: ${{ secrets.EDGEEVER_APPLE_API_ISSUER }}",
);
expect(workflow).toContain(
"APP_STORE_CONNECT_API_KEY_ID: ${{ secrets.EDGEEVER_APPLE_API_KEY_ID }}",
);
expect(workflow).toContain(
"APP_STORE_CONNECT_API_KEY_P8_BASE64: ${{ secrets.EDGEEVER_APPLE_API_KEY_BASE64 }}",
);
expect(workflow).toContain(
'PRECHECK_INCLUDE_IN_APP_PURCHASES: "false"',
);
expect(workflow).toContain("for attempt in {1..20}");
expect(workflow).toContain(
'Build number: ${APP_STORE_BUILD_NUMBER} does not exist',
);
expect(workflow).toContain("retrying in 60 seconds (${attempt}/20)");
const fastfile = readFileSync(
new URL("../apps/ios/fastlane/Fastfile", import.meta.url),
"utf8",
);
expect(fastfile).toContain("precheck_include_in_app_purchases: false");
expect(fastfile).toContain("APP_STORE_RELEASE_NOTES_JA is required");
});
test("replaces the GitHub APK with the Play-signed universal APK", () => {
const workflow = readFileSync(
new URL("../.github/workflows/store-delivery.yml", import.meta.url),
"utf8",
);
expect(workflow).toContain("GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_BASE64");
expect(workflow).toContain("ANDROID_PLAY_APP_SIGNER_SHA256");
expect(workflow).toContain("scripts/download-play-universal-apk.mjs");
expect(workflow).toContain('gh release upload "$RELEASE_TAG" "$apk_path"');
expect(
workflow.match(/if: \$\{\{ !inputs\.recover_play_apk \}\}/g),
).toHaveLength(6);
expect(workflow).toContain("if: ${{ inputs.recover_play_apk }}");
expect(workflow).toContain(
"node --input-type=module -e \"await import('google-auth-library')\"",
);
});
test("can prepare a matching Draft before formal publication", () => {
const workflow = readFileSync(
new URL("../.github/workflows/store-delivery.yml", import.meta.url),
"utf8",
);
expect(workflow).toContain('if [[ "$release_is_draft" = "true" ]]');
expect(workflow).toContain('previous_tag="${release_tags[0]:-}"');
expect(workflow).toContain('git tag "$RELEASE_TAG" "$RELEASE_TARGET"');
expect(workflow).toContain("set -o pipefail");
expect(workflow).toContain(
"release_target: ${{ steps.release.outputs.release_target }}",
);
expect(workflow).toContain('gh release view "$RELEASE_TAG"');
expect(workflow).not.toContain("releases/tags/${RELEASE_TAG}");
expect(workflow).toContain(
"ref: ${{ steps.release.outputs.release_target }}",
);
expect(
workflow.match(/ref: \$\{\{ needs\.plan\.outputs\.release_target \}\}/g),
).toHaveLength(2);
expect(workflow).not.toContain("ref: ${{ inputs.release_tag }}");
expect(workflow).not.toContain(
'test "$(jq -r \'.draft\' <<<"$release")" = "false"',
);
});
test("uses a dedicated Draft audit that accepts only the Play app signer", () => {
const workflow = readFileSync(
new URL(
"../.github/workflows/android-play-signature-audit.yml",
import.meta.url,
),
"utf8",
);
expect(workflow).toContain("github.repository == 'tianma-if/edgeever'");
expect(workflow).toContain(
"permissions:\n # Draft Releases are only visible to a token with repository write access.\n contents: write",
);
expect(workflow).toContain('gh release view "$RELEASE_TAG"');
expect(workflow).toContain(
'test "$(jq -r \'.isDraft\' <<<"$release")" = "true"',
);
expect(workflow).toContain(
"ref: ${{ steps.release.outputs.target_commitish }}",
);
expect(workflow).not.toContain("ref: ${{ inputs.release_tag }}");
expect(workflow).toContain(
"EDGE_EVER_ANDROID_ALLOWED_SIGNER_SHA256: ${{ secrets.ANDROID_PLAY_APP_SIGNER_SHA256 }}",
);
expect(workflow).not.toContain(
"22bf52a9501c89020f5acc966960152c826bfa64f31e578e858d088f8cd75d87",
);
});
test("keeps the Play bundle and generated Release APK arm64-only", () => {
const workflow = readFileSync(
new URL("../.github/workflows/store-delivery.yml", import.meta.url),
"utf8",
);
const buildScript = readFileSync(
new URL("./build-android-local.sh", import.meta.url),
"utf8",
);
expect(workflow).toContain("EDGE_EVER_ANDROID_PLAY_ARCHS: arm64-v8a");
expect(workflow).toContain("Verify Play APK architecture");
expect(workflow).toContain('if [[ "$actual_archs" != "arm64-v8a" ]]');
expect(buildScript).toContain(
'PLAY_ARCHS="${EDGE_EVER_ANDROID_PLAY_ARCHS:-arm64-v8a}"',
);
expect(buildScript).toContain(
'if [[ "$ACTUAL_PLAY_ARCHS" != "$PLAY_ARCHS" ]]',
);
expect(buildScript).not.toContain("EDGE_EVER_ANDROID_ARCHS:-");
expect(buildScript).not.toContain("armeabi-v7a,arm64-v8a,x86,x86_64");
});
test("keeps the self-update install permission out of Android builds", () => {
const buildScript = readFileSync(
new URL("./build-android-local.sh", import.meta.url),
"utf8",
);
const appConfig = readFileSync(
new URL("../apps/mobile/app.json", import.meta.url),
"utf8",
);
expect(appConfig).not.toContain("android.permission.REQUEST_INSTALL_PACKAGES");
expect(buildScript).not.toContain("configure-android-package-permissions");
expect(buildScript).toContain(
'grep -q "android.permission.REQUEST_INSTALL_PACKAGES" "$PACKAGED_MANIFEST"',
);
});
});