572 lines
23 KiB
JavaScript
572 lines
23 KiB
JavaScript
'use strict';
|
|
|
|
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const { execFileSync } = require('node:child_process');
|
|
const {
|
|
normalizePath,
|
|
isValidSha,
|
|
isZeroSha,
|
|
isAppPath,
|
|
isCiPath,
|
|
classifyFiles,
|
|
getChangedFiles,
|
|
detectAndroidChanges
|
|
} = require('./detect-android-changes.cjs');
|
|
|
|
describe('Path Normalization and Validation', () => {
|
|
it('normalizes Windows backslashes and leading dot-slash', () => {
|
|
assert.strictEqual(normalizePath('lib\\main.dart'), 'lib/main.dart');
|
|
assert.strictEqual(normalizePath('.\\android\\app\\build.gradle'), 'android/app/build.gradle');
|
|
assert.strictEqual(normalizePath(' test/widget_test.dart '), 'test/widget_test.dart');
|
|
assert.strictEqual(normalizePath(null), '');
|
|
assert.strictEqual(normalizePath(undefined), '');
|
|
});
|
|
|
|
it('validates 40-character hex git SHAs', () => {
|
|
assert.strictEqual(isValidSha('03446911949ff8b27543280bfe226d9babd6fd7a'), true);
|
|
assert.strictEqual(isValidSha('0000000000000000000000000000000000000000'), true);
|
|
assert.strictEqual(isValidSha('abc123'), false);
|
|
assert.strictEqual(isValidSha(''), false);
|
|
assert.strictEqual(isValidSha(null), false);
|
|
assert.strictEqual(isValidSha('03446911949ff8b27543280bfe226d9babd6fd7z'), false);
|
|
});
|
|
|
|
it('identifies 40-zero SHAs', () => {
|
|
assert.strictEqual(isZeroSha('0000000000000000000000000000000000000000'), true);
|
|
assert.strictEqual(isZeroSha('03446911949ff8b27543280bfe226d9babd6fd7a'), false);
|
|
assert.strictEqual(isZeroSha(''), false);
|
|
});
|
|
});
|
|
|
|
describe('File Path Classification', () => {
|
|
it('identifies Android/Flutter app paths correctly', () => {
|
|
assert.strictEqual(isAppPath('lib/main.dart'), true);
|
|
assert.strictEqual(isAppPath('lib/services/telemetry_service.dart'), true);
|
|
assert.strictEqual(isAppPath('test/unit/telemetry_test.dart'), true);
|
|
assert.strictEqual(isAppPath('integration_test/app_test.dart'), true);
|
|
assert.strictEqual(isAppPath('android/app/build.gradle'), true);
|
|
assert.strictEqual(isAppPath('android/gradlew'), true);
|
|
assert.strictEqual(isAppPath('android/local.properties'), true);
|
|
assert.strictEqual(isAppPath('assets/images/logo.png'), true);
|
|
assert.strictEqual(isAppPath('pubspec.yaml'), true);
|
|
assert.strictEqual(isAppPath('pubspec.lock'), true);
|
|
assert.strictEqual(isAppPath('analysis_options.yaml'), true);
|
|
assert.strictEqual(isAppPath('build.yaml'), true);
|
|
|
|
// Negative matches for app paths
|
|
assert.strictEqual(isAppPath('website/components/ops/support-report-modal.tsx'), false);
|
|
assert.strictEqual(isAppPath('backend/motorsignal_api/app/main.py'), false);
|
|
assert.strictEqual(isAppPath('docs/agents/issue-tracker.md'), false);
|
|
assert.strictEqual(isAppPath('firebase/firestore.rules'), false);
|
|
assert.strictEqual(isAppPath('server/waze_relay/index.js'), false);
|
|
assert.strictEqual(isAppPath('lib_unrelated.txt'), false);
|
|
assert.strictEqual(isAppPath('test-runner.js'), false);
|
|
});
|
|
|
|
it('identifies Android CI paths correctly', () => {
|
|
assert.strictEqual(isCiPath('.github/workflows/android-build.yml'), true);
|
|
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.cjs'), true);
|
|
assert.strictEqual(isCiPath('.github/scripts/detect-android-changes.test.cjs'), true);
|
|
|
|
// Negative matches for Android CI paths
|
|
assert.strictEqual(isCiPath('.github/workflows/website-ci.yml'), false);
|
|
assert.strictEqual(isCiPath('.github/workflows/backend-ci.yml'), false);
|
|
assert.strictEqual(isCiPath('.github/workflows/waze-relay-ci.yml'), false);
|
|
assert.strictEqual(isCiPath('.github/workflows/firebase-emulator-tests.yml'), false);
|
|
assert.strictEqual(isCiPath('.github/pull_request_template.md'), false);
|
|
});
|
|
});
|
|
|
|
describe('classifyFiles Scenarios', () => {
|
|
it('classifies website-only changes as false', () => {
|
|
const files = [
|
|
'website/components/ops/support-report-modal.tsx',
|
|
'website/package.json',
|
|
'website/tests/support-report-modal.test.tsx',
|
|
'website/app/page.tsx'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
assert.strictEqual(result.ciFiles.length, 0);
|
|
assert.strictEqual(result.unrelatedFiles.length, 4);
|
|
});
|
|
|
|
it('classifies backend-only changes as false', () => {
|
|
const files = [
|
|
'backend/motorsignal_api/app/main.py',
|
|
'backend/motorsignal_api/app/models.py',
|
|
'backend/motorsignal_api/tests/test_ops_actions.py',
|
|
'backend/motorsignal_api/alembic/versions/f1a2b3c4d5e7_add_ops_actions.py'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
assert.strictEqual(result.ciFiles.length, 0);
|
|
assert.strictEqual(result.unrelatedFiles.length, 4);
|
|
});
|
|
|
|
it('classifies docs-only changes as false', () => {
|
|
const files = [
|
|
'README.md',
|
|
'docs/agents/issue-tracker.md',
|
|
'AGENTS.md',
|
|
'CHANGELOG.md',
|
|
'DESIGN.md'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
assert.strictEqual(result.ciFiles.length, 0);
|
|
assert.strictEqual(result.unrelatedFiles.length, 5);
|
|
});
|
|
|
|
it('classifies Firebase-rules-only and Waze-relay-only changes as false', () => {
|
|
const files = [
|
|
'firebase/firestore.rules',
|
|
'firebase.json',
|
|
'server/waze_relay/index.js',
|
|
'server/waze_relay/package.json'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
|
|
it('classifies Flutter lib change as true', () => {
|
|
const files = ['lib/telemetry/telemetry_service.dart'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.deepStrictEqual(result.appFiles, ['lib/telemetry/telemetry_service.dart']);
|
|
});
|
|
|
|
it('classifies Flutter test change as true', () => {
|
|
const files = ['test/unit/telemetry_test.dart'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.deepStrictEqual(result.appFiles, ['test/unit/telemetry_test.dart']);
|
|
});
|
|
|
|
it('classifies Flutter integration_test change as true', () => {
|
|
const files = ['integration_test/telemetry_soak_test.dart'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('classifies Android Gradle change as true', () => {
|
|
const files = ['android/app/build.gradle'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('classifies pubspec change as true', () => {
|
|
const files = ['pubspec.yaml'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
|
|
const lockFiles = ['pubspec.lock'];
|
|
const lockResult = classifyFiles(lockFiles);
|
|
assert.strictEqual(lockResult.android_changed, true);
|
|
assert.strictEqual(lockResult.app_changed, true);
|
|
});
|
|
|
|
it('classifies analysis_options and build.yaml changes as true', () => {
|
|
assert.strictEqual(classifyFiles(['analysis_options.yaml']).android_changed, true);
|
|
assert.strictEqual(classifyFiles(['build.yaml']).android_changed, true);
|
|
});
|
|
|
|
it('classifies assets change as true', () => {
|
|
const files = ['assets/telemetry/sample.json'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('classifies mixed website + Flutter change as true', () => {
|
|
const files = [
|
|
'website/app/page.tsx',
|
|
'website/package.json',
|
|
'lib/models/ride.dart'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.strictEqual(result.appFiles.length, 1);
|
|
assert.strictEqual(result.unrelatedFiles.length, 2);
|
|
});
|
|
|
|
it('classifies Android workflow change as android_changed=true but app_changed=false', () => {
|
|
const files = ['.github/workflows/android-build.yml'];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.ciFiles.length, 1);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
});
|
|
|
|
it('classifies detector script change as android_changed=true but app_changed=false', () => {
|
|
const files = [
|
|
'.github/scripts/detect-android-changes.cjs',
|
|
'.github/scripts/detect-android-changes.test.cjs'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.ciFiles.length, 2);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
});
|
|
|
|
it('classifies other workflow changes (website/backend CI) as false', () => {
|
|
const files = [
|
|
'.github/workflows/website-ci.yml',
|
|
'.github/workflows/backend-ci.yml',
|
|
'.github/workflows/waze-relay-ci.yml',
|
|
'.github/workflows/firebase-emulator-tests.yml'
|
|
];
|
|
const result = classifyFiles(files);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
});
|
|
|
|
describe('detectAndroidChanges Workflow Event Logic', () => {
|
|
const sampleBase = '03446911949ff8b27543280bfe226d9babd6fd7a';
|
|
const sampleHead = '247ccde71e4378dacd45121c891f45aad5ed2424';
|
|
|
|
it('always triggers full build on workflow_dispatch', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'workflow_dispatch',
|
|
baseSha: '',
|
|
headSha: ''
|
|
});
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.match(result.reason, /Manual workflow_dispatch/);
|
|
});
|
|
|
|
it('fails safe to true on zero push base SHA', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha: '0000000000000000000000000000000000000000',
|
|
headSha: sampleHead
|
|
});
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.match(result.reason, /failing safe/);
|
|
});
|
|
|
|
it('fails safe to true on missing base SHA', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: '',
|
|
headSha: sampleHead
|
|
});
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('fails safe to true on invalid base SHA format', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: 'malicious-string; rm -rf /',
|
|
headSha: sampleHead
|
|
});
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('fails safe to true on invalid head SHA format', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: sampleBase,
|
|
headSha: 'bad-sha'
|
|
});
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('reports false when base and head SHA are identical', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: sampleBase,
|
|
headSha: sampleBase
|
|
});
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.match(result.reason, /identical/);
|
|
});
|
|
|
|
it('fails safe to true when git diff comparison throws an error', () => {
|
|
const faultyDeps = {
|
|
getChangedFilesFn: () => {
|
|
throw new Error('fatal: bad object missing-commit');
|
|
}
|
|
};
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: sampleBase,
|
|
headSha: sampleHead
|
|
}, faultyDeps);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.match(result.reason, /failing safe/);
|
|
});
|
|
|
|
it('returns false for pull_request with only website files', () => {
|
|
const mockDeps = {
|
|
getChangedFilesFn: () => ['website/app/page.tsx', 'website/package.json']
|
|
};
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: sampleBase,
|
|
headSha: sampleHead
|
|
}, mockDeps);
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
|
|
it('returns true for pull_request with Flutter lib file', () => {
|
|
const mockDeps = {
|
|
getChangedFilesFn: () => ['lib/main.dart']
|
|
};
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha: sampleBase,
|
|
headSha: sampleHead
|
|
}, mockDeps);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('returns android_changed=true, app_changed=false for workflow change', () => {
|
|
const mockDeps = {
|
|
getChangedFilesFn: () => ['.github/workflows/android-build.yml']
|
|
};
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha: sampleBase,
|
|
headSha: sampleHead
|
|
}, mockDeps);
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
});
|
|
|
|
describe('Live Git Repository Verification', () => {
|
|
it('correctly compares historical non-Android commit in the repository', () => {
|
|
// 03446911949ff8b27543280bfe226d9babd6fd7a was the PR #15 merge (website and backend changes only).
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha: '7efd29a214d01bd1c167fabf75f5c90cea5da215',
|
|
headSha: '03446911949ff8b27543280bfe226d9babd6fd7a'
|
|
});
|
|
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
assert.strictEqual(result.ciFiles.length, 0);
|
|
assert.ok(result.unrelatedFiles.length > 0);
|
|
});
|
|
|
|
it('correctly compares historical Flutter commit in the repository', () => {
|
|
// 68d4539f90199e2428fafbb7de779ff2dfb22b4e modified Flutter lib/ and test/ files.
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha: 'e784c4895dcbc6abbfaa516ac34f3cedbc54384a',
|
|
headSha: '68d4539f90199e2428fafbb7de779ff2dfb22b4e'
|
|
});
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.ok(result.appFiles.length > 0);
|
|
});
|
|
|
|
it('deterministically classifies workflow-only changes in a temporary repository fixture', () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'detector-fixture-workflow-'));
|
|
try {
|
|
execFileSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'user.name', 'CI Fixture Test'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'user.email', 'fixture@example.com'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'commit.gpgSign', 'false'], { cwd: tmpDir, stdio: 'ignore' });
|
|
|
|
// Base commit: unrelated website file
|
|
fs.mkdirSync(path.join(tmpDir, 'website'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, 'website', 'page.tsx'), 'export default function Page() {}\n', 'utf8');
|
|
execFileSync('git', ['add', 'website/page.tsx'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['commit', '-m', 'Add website page'], { cwd: tmpDir, stdio: 'ignore' });
|
|
const baseSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tmpDir, encoding: 'utf8' }).trim();
|
|
|
|
// Head commit: .github/workflows/android-build.yml
|
|
fs.mkdirSync(path.join(tmpDir, '.github', 'workflows'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, '.github', 'workflows', 'android-build.yml'), 'name: MotorSignal Android CI\n', 'utf8');
|
|
execFileSync('git', ['add', '.github/workflows/android-build.yml'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['commit', '-m', 'Add android-build workflow'], { cwd: tmpDir, stdio: 'ignore' });
|
|
const headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tmpDir, encoding: 'utf8' }).trim();
|
|
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha,
|
|
headSha,
|
|
repoDir: tmpDir
|
|
});
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.ok(result.ciFiles.includes('.github/workflows/android-build.yml'));
|
|
assert.strictEqual(result.appFiles.length, 0);
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it('deterministically classifies mixed workflow and application changes in a temporary repository fixture', () => {
|
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'detector-fixture-mixed-'));
|
|
try {
|
|
execFileSync('git', ['init'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'user.name', 'CI Fixture Test'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'user.email', 'fixture@example.com'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['config', 'commit.gpgSign', 'false'], { cwd: tmpDir, stdio: 'ignore' });
|
|
|
|
// Base commit: unrelated website file
|
|
fs.mkdirSync(path.join(tmpDir, 'website'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, 'website', 'page.tsx'), 'export default function Page() {}\n', 'utf8');
|
|
execFileSync('git', ['add', 'website/page.tsx'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['commit', '-m', 'Add website page'], { cwd: tmpDir, stdio: 'ignore' });
|
|
const baseSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tmpDir, encoding: 'utf8' }).trim();
|
|
|
|
// Head commit: both .github/workflows/android-build.yml and lib/main.dart
|
|
fs.mkdirSync(path.join(tmpDir, '.github', 'workflows'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, '.github', 'workflows', 'android-build.yml'), 'name: MotorSignal Android CI\n', 'utf8');
|
|
fs.mkdirSync(path.join(tmpDir, 'lib'), { recursive: true });
|
|
fs.writeFileSync(path.join(tmpDir, 'lib', 'main.dart'), 'void main() {}\n', 'utf8');
|
|
execFileSync('git', ['add', '.github/workflows/android-build.yml', 'lib/main.dart'], { cwd: tmpDir, stdio: 'ignore' });
|
|
execFileSync('git', ['commit', '-m', 'Add workflow and Flutter app code'], { cwd: tmpDir, stdio: 'ignore' });
|
|
const headSha = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: tmpDir, encoding: 'utf8' }).trim();
|
|
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha,
|
|
headSha,
|
|
repoDir: tmpDir
|
|
});
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
assert.ok(result.ciFiles.includes('.github/workflows/android-build.yml'));
|
|
assert.ok(result.appFiles.includes('lib/main.dart'));
|
|
} finally {
|
|
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Workflow Scenario Matrix Verification', () => {
|
|
const baseSha = '03446911949ff8b27543280bfe226d9babd6fd7a';
|
|
const headSha = '247ccde71e4378dacd45121c891f45aad5ed2424';
|
|
|
|
it('website-only PR skips heavy build and release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['website/app/page.tsx', 'website/package.json'] });
|
|
|
|
const heavyBuildRuns = result.android_changed;
|
|
const releaseRuns = false; // PR event cannot run release
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
assert.strictEqual(heavyBuildRuns, false);
|
|
assert.strictEqual(releaseRuns, false);
|
|
});
|
|
|
|
it('backend-only PR skips heavy build and release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['backend/motorsignal_api/app/main.py'] });
|
|
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
|
|
it('Flutter PR runs heavy build and skips release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['lib/main.dart', 'test/widget_test.dart'] });
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('website + Flutter PR runs heavy build and skips release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'pull_request',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['website/app/page.tsx', 'lib/services/telemetry.dart'] });
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('master push after website merge skips heavy build and release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['website/components/ops/support-report-modal.tsx'] });
|
|
|
|
assert.strictEqual(result.android_changed, false);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
|
|
it('master push after Flutter merge runs heavy build and signed release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['lib/telemetry/telemetry_service.dart'] });
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
|
|
it('master push after workflow-only change runs heavy build but skips signed release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'push',
|
|
baseSha,
|
|
headSha
|
|
}, { getChangedFilesFn: () => ['.github/workflows/android-build.yml'] });
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, false);
|
|
});
|
|
|
|
it('manual dispatch runs heavy build and signed release', () => {
|
|
const result = detectAndroidChanges({
|
|
eventName: 'workflow_dispatch',
|
|
baseSha: '',
|
|
headSha: ''
|
|
});
|
|
|
|
assert.strictEqual(result.android_changed, true);
|
|
assert.strictEqual(result.app_changed, true);
|
|
});
|
|
});
|
|
|