Skip to content

Test coverage for codesigned updates - #51

Draft
ofalvai wants to merge 1 commit into
masterfrom
push-qntptxzvpzzy
Draft

Test coverage for codesigned updates#51
ofalvai wants to merge 1 commit into
masterfrom
push-qntptxzvpzzy

Conversation

@ofalvai

@ofalvai ofalvai commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Why

We want to make bigger changes to the iOS package codesigning codepath, but integration tests don't test codesigned updates at the moment, so that codepath is not covered with tests.

What

Instead of adding new test cases for codesigned updates, this PR makes existing test cases run codesigned updates. Signature verification is an additional step during package install, we don't lose test coverage by testing codesigned updates.

Changes:

  • A dummy keypair is checked into the repo as a test fixture
  • The test harness creates the .codepushrelease JWT token for each update package
  • Test harness also sets CodePushUpdateKey for the test apps, enabling signature checking behavior
  • Add one more integration test case for the signature mismatch scenario

@ofalvai
ofalvai requested a balanced review from Copilot September 1, 2026 12:35
Comment thread test/codesign.ts
Comment on lines +12 to +49
function isHashIgnored(relativePath: string): boolean {
return relativePath.startsWith("__MACOSX/")
|| relativePath === ".DS_Store"
|| relativePath.endsWith("/.DS_Store")
|| relativePath === CODEPUSH_METADATA_FILE_NAME
|| relativePath.endsWith(`/${CODEPUSH_METADATA_FILE_NAME}`);
}

/**
* Computes the same content hash that the native SDKs compute over an installed update folder, so the mock server
* can hand back a package_hash that will actually match what the client expects.
*/
export function computeUpdateContentsHash(folderPath: string): string {
const manifest: string[] = [];

const walk = (currentPath: string, relativePrefix: string) => {
for (const entryName of fs.readdirSync(currentPath)) {
const entryPath = path.join(currentPath, entryName);
const relativePath = relativePrefix ? `${relativePrefix}/${entryName}` : entryName;

if (isHashIgnored(relativePath)) {
continue;
}

if (fs.statSync(entryPath).isDirectory()) {
walk(entryPath, relativePath);
} else {
const fileHash = crypto.createHash("sha256").update(fs.readFileSync(entryPath)).digest("hex");
manifest.push(`${relativePath}:${fileHash}`);
}
}
};

walk(folderPath, "");
manifest.sort();

return crypto.createHash("sha256").update(JSON.stringify(manifest)).digest("hex");
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved from test.ts, not new code

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds integration coverage for signed CodePush updates across native platforms.

Changes:

  • Signs generated update archives using a test RSA keypair.
  • Configures native test applications with the public key.
  • Adds a tampered-signature rejection scenario.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
test/test.ts Integrates signing and adds signature validation coverage.
test/codesign.ts Implements hashing, JWT signing, and tampering helpers.
test/fixtures/codesigning/test-public-key.pem Adds the test public key.
test/fixtures/codesigning/test-private-key.pem Adds the test private key.
Suppressed comments (1)

test/test.ts:247

  • The --expo iOS path returns from installPlatform at line 234 before this plist replacement runs. Expo iOS is a supported integration-test mode (package.json:28,33,38), so its app has no public key: signed updates are not actually verified, and the tampered-signature test will receive DOWNLOAD_SUCCEEDED rather than the expected DOWNLOAD_ERROR. Add CodePushPublicKey to the generated Expo Info.plist before that branch returns too.
                    "<key>CodePushDeploymentKey</key>\n\t<string>" + this.getDefaultDeploymentKey() + "</string>\n\t<key>CodePushServerURL</key>\n\t<string>" + this.getServerUrl() + "</string>\n\t<key>CodePushPublicKey</key>\n\t<string>" + codeSigningPublicKey + "</string>\n\t</dict>\n</plist>"))

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/test.ts
const string = path.join(innerprojectDirectory, "android", "app", "src", "main", "res", "values", "strings.xml");
TestUtil.replaceString(string, TestUtil.SERVER_URL_PLACEHOLDER, this.getServerUrl());
TestUtil.replaceString(string, TestUtil.ANDROID_KEY_PLACEHOLDER, this.getDefaultDeploymentKey());
TestUtil.replaceString(string, "</resources>", `<string moduleConfig="true" name="CodePushPublicKey">${codeSigningPublicKey}</string>\n</resources>`);
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