Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ reviews:
enabled: true
drafts: false
base_branches: ["main", "dev"]
tools:
github-checks:
timeout_ms: 300000
path_instructions:
- path: "app/src/**/*.{ts,tsx,js,jsx}"
instructions: |
Expand Down
58 changes: 58 additions & 0 deletions app/fastlane/test/app_name_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
require "minitest/autorun"
require "json"
require "tmpdir"
require "fileutils"
require_relative "../helpers"

class AppNameTest < Minitest::Test
def setup
@orig_env = ENV.to_h
@tmp = Dir.mktmpdir
end

def teardown
FileUtils.remove_entry(@tmp)
ENV.clear
ENV.update(@orig_env)
end

def write_app_json(content)
File.write(File.join(@tmp, "app.json"), content)
end

def evaluate_app_name
ENV["IOS_PROJECT_NAME"] || begin
app_json_path = File.join(@tmp, "app.json")
if File.exist?(app_json_path)
app_config = JSON.parse(File.read(app_json_path))
app_config["displayName"] if app_config.is_a?(Hash)
end
rescue JSON::ParserError, Errno::ENOENT
Fastlane::UI.ui_object.important("Could not read app.json or invalid JSON format, using default app name")
nil
end || "MobileApp"
end

def test_env_variable_precedence
ENV["IOS_PROJECT_NAME"] = "EnvApp"
assert_equal "EnvApp", evaluate_app_name
end

def test_display_name_from_app_json
ENV.delete("IOS_PROJECT_NAME")
write_app_json({ displayName: "JsonApp" }.to_json)
assert_equal "JsonApp", evaluate_app_name
end

def test_default_when_app_json_missing_or_malformed
ENV.delete("IOS_PROJECT_NAME")
write_app_json("{ invalid json")
messages = []
ui_obj = Fastlane::UI.ui_object
orig = ui_obj.method(:important)
ui_obj.define_singleton_method(:important) { |msg| messages << msg }
assert_equal "MobileApp", evaluate_app_name
assert_includes messages.first, "Could not read app.json"
ui_obj.define_singleton_method(:important, orig)
end
end
37 changes: 37 additions & 0 deletions app/fastlane/test/helpers_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,43 @@ def test_upload_file_to_slack_missing_channel
ENV.delete("SLACK_API_TOKEN")
end

def test_upload_file_to_slack_missing_token
ENV.delete("SLACK_API_TOKEN")
file = Tempfile.new(["artifact", ".txt"])
file.write("data")
file.close

assert_raises(FastlaneCore::Interface::FastlaneCommonException) do
Fastlane::Helpers.upload_file_to_slack(file_path: file.path, channel_id: "C123")
end
ensure
file.unlink
end

def test_slack_deploy_source_messages
file = Tempfile.new(["artifact", ".txt"])
file.write("data")
file.close

%w[true nil].each do |ci_value|
ENV["CI"] = ci_value == "true" ? "true" : nil
captured = nil
Fastlane::Helpers.stub(:upload_file_to_slack, ->(**args) { captured = args }) do
deploy_source = Fastlane::Helpers.is_ci_environment? ? "GitHub Workflow" : "Local Deploy"
Fastlane::Helpers.upload_file_to_slack(
file_path: file.path,
channel_id: "C123",
initial_comment: "Deploy via #{deploy_source}",
)
end
expected = ci_value == "true" ? "GitHub Workflow" : "Local Deploy"
assert_includes captured[:initial_comment], expected
end
ensure
file.unlink
ENV.delete("CI")
end

private

def clear_test_env_vars
Expand Down
27 changes: 20 additions & 7 deletions app/scripts/mobile-deploy-confirm.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
let { execSync } = require('child_process');

// Constants
const DEPLOYMENT_METHODS = {
Expand Down Expand Up @@ -418,7 +418,7 @@ function getFastlaneCommands(platform) {
* Executes iOS build cleanup script
* @param {string} platform - Target platform
*/
function performIOSBuildCleanup(platform) {
let performIOSBuildCleanup = function (platform) {
// Only run cleanup for iOS deployments
if (platform !== PLATFORMS.IOS && platform !== PLATFORMS.BOTH) {
return;
Expand All @@ -442,7 +442,7 @@ function performIOSBuildCleanup(platform) {
);
// Don't exit on cleanup failure - it's not critical
}
}
};

/**
* Executes local fastlane deployment
Expand Down Expand Up @@ -576,7 +576,20 @@ async function main() {
}

// Execute main function
main().catch(error => {
console.error(`${CONSOLE_SYMBOLS.ERROR} Error:`, error.message);
process.exit(1);
});
if (require.main === module) {
main().catch(error => {
console.error(`${CONSOLE_SYMBOLS.ERROR} Error:`, error.message);
process.exit(1);
});
} else {
module.exports = {
performIOSBuildCleanup,
executeLocalFastlaneDeployment,
_setExecSync: fn => {
execSync = fn;
},
_setPerformIOSBuildCleanup: fn => {
performIOSBuildCleanup = fn;
},
};
}
47 changes: 46 additions & 1 deletion app/scripts/tests/cleanup-ios-build.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ const fs = require('fs');
const path = require('path');
const os = require('os');
const { execSync } = require('child_process');
const { spawnSync } = require('child_process');
const { describe, it } = require('node:test');
const assert = require('node:assert');

const SCRIPT = path.join(__dirname, '../cleanup-ios-build.sh');
const SCRIPT = path.resolve(__dirname, '../cleanup-ios-build.sh');

describe('cleanup-ios-build.sh', () => {
it('resets pbxproj and reapplies versions', () => {
Expand Down Expand Up @@ -40,4 +41,48 @@ describe('cleanup-ios-build.sh', () => {
assert(result.includes('MARKETING_VERSION = 2.0.0;'));
assert(!result.includes('SomeArtifact'));
});

it('fails when the pbxproj file does not exist', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cleanup-test-'));

const result = spawnSync('bash', [SCRIPT], {
cwd: tmp,
env: { ...process.env, IOS_PROJECT_NAME: 'MissingProject' },
encoding: 'utf8',
});

assert.notStrictEqual(result.status, 0);
assert(result.stderr.includes('Project file not found'));
});

it('fails when version information cannot be extracted', () => {
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'cleanup-test-'));
const projectName = 'BadApp';
const iosDir = path.join(tmp, 'ios', `${projectName}.xcodeproj`);
fs.mkdirSync(iosDir, { recursive: true });
const pbxPath = path.join(iosDir, 'project.pbxproj');
fs.writeFileSync(
pbxPath,
'CURRENT_PROJECT_VERSION = ;\nMARKETING_VERSION = ;\n',
);

const cwd = process.cwd();
process.chdir(tmp);
execSync('git init -q');
execSync('git config user.email "[email protected]"');
execSync('git config user.name "Test"');
execSync(`git add ${pbxPath}`);
execSync('git commit -m init -q');

const result = spawnSync('bash', [SCRIPT], {
cwd: tmp,
env: { ...process.env, IOS_PROJECT_NAME: projectName },
encoding: 'utf8',
});

process.chdir(cwd);

assert.notStrictEqual(result.status, 0);
assert(result.stderr.includes('Failed to extract version information'));
});
});
48 changes: 48 additions & 0 deletions app/scripts/tests/mobile-deploy-confirm-module.test.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const { describe, it } = require('node:test');
const assert = require('node:assert');
const child_process = require('child_process');

describe('performIOSBuildCleanup', () => {
it('executes cleanup script for ios', () => {
const deploy = require('../mobile-deploy-confirm.cjs');
let called = null;
const original = child_process.execSync;
deploy._setExecSync(cmd => {
called = cmd;
});
deploy.performIOSBuildCleanup('ios');
deploy._setExecSync(original);
assert(called && called.includes('cleanup-ios-build.sh'));
});

it('does nothing for android', () => {
const deploy = require('../mobile-deploy-confirm.cjs');
let called = false;
const original = child_process.execSync;
deploy._setExecSync(() => {
called = true;
});
deploy.performIOSBuildCleanup('android');
deploy._setExecSync(original);
assert.strictEqual(called, false);
});
});

describe('executeLocalFastlaneDeployment', () => {
it('invokes cleanup after deployment', async () => {
const deploy = require('../mobile-deploy-confirm.cjs');
deploy._setExecSync(() => {});

let cleanupCalled = false;
const originalCleanup = deploy.performIOSBuildCleanup;
deploy._setPerformIOSBuildCleanup(() => {
cleanupCalled = true;
});

await deploy.executeLocalFastlaneDeployment('ios');

deploy._setPerformIOSBuildCleanup(originalCleanup);
deploy._setExecSync(child_process.execSync);
assert.strictEqual(cleanupCalled, true);
});
});
Loading