76 lines
4.8 KiB
TypeScript
76 lines
4.8 KiB
TypeScript
// fastlane left in a project (Fastfile, CI calls to supply / deliver / pilot) is found and named (pitfall 96).
|
|
import { describe, expect, test } from 'bun:test';
|
|
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
import { findFastlaneTraces, traceLine, traceMessage } from '../cli/lib/fastlane-traces';
|
|
import { runScan } from '../cli/lib/scan';
|
|
|
|
function project(files: Record<string, string>): string {
|
|
const dir = mkdtempSync(join(tmpdir(), 'sb-fl-'));
|
|
for (const [p, text] of Object.entries(files)) { mkdirSync(join(dir, p, '..'), { recursive: true }); writeFileSync(join(dir, p), text); }
|
|
return dir;
|
|
}
|
|
|
|
describe('fastlane traces', () => {
|
|
test('Fastfile actions, Appfile, and CI / script calls with file:line', () => {
|
|
const dir = project({
|
|
'android/fastlane/Fastfile': 'lane :ship do\n # supply in a comment is not a call\n supply(track: "internal")\nend\n',
|
|
'fastlane/Appfile': 'app_identifier("com.example.game")\n',
|
|
'Jenkinsfile': 'stage("upload") {\n sh "bundle exec fastlane supply --aab app.aab"\n}\n',
|
|
'.github/workflows/ios.yml': 'steps:\n - run: fastlane pilot upload\n',
|
|
'scripts/release.sh': 'echo fastlane is fine to mention\n',
|
|
});
|
|
const t = findFastlaneTraces(dir);
|
|
expect(t).toContainEqual({ file: 'android/fastlane/Fastfile', line: 3, text: 'supply(track: "internal")', store: 'play' });
|
|
expect(t).toContainEqual({ file: 'fastlane/Appfile', line: 1, text: 'app_identifier("com.example.game")', store: 'both' });
|
|
expect(t).toContainEqual({ file: 'Jenkinsfile', line: 2, text: 'sh "bundle exec fastlane supply --aab app.aab"', store: 'play' });
|
|
expect(t).toContainEqual({ file: '.github/workflows/ios.yml', line: 2, text: '- run: fastlane pilot upload', store: 'apple' });
|
|
expect(t.some(x => x.file === 'scripts/release.sh')).toBe(false);
|
|
expect(t.filter(x => x.file === 'android/fastlane/Fastfile')).toHaveLength(1);
|
|
expect(traceLine(t.find(x => x.file === 'Jenkinsfile')!)).toBe('fastlane in Jenkinsfile:2: sh "bundle exec fastlane supply --aab app.aab"');
|
|
});
|
|
test('scripts that call the store APIs or upload tools themselves, with the storebound way', () => {
|
|
const dir = project({
|
|
'scripts/play-upload.mjs': "const url = 'https://androidpublisher.googleapis.com/upload/androidpublisher/v3/applications/com.example.app/edits';\n",
|
|
'tools/asc.py': "BASE = 'https://api.appstoreconnect.apple.com/v1'\n",
|
|
'deploy/ios.sh': 'xcrun altool --upload-app -f app.ipa\n',
|
|
'package.json': '{"name":"an-app","scripts":{"ship":"node scripts/play-upload.mjs"}}',
|
|
});
|
|
const t = findFastlaneTraces(dir);
|
|
expect(t.map(x => `${x.file} ${x.store} ${x.via}`).sort()).toEqual(['deploy/ios.sh apple api', 'scripts/play-upload.mjs play api', 'tools/asc.py apple api']);
|
|
expect(traceMessage(t.find(x => x.store === 'play')!)).toContain('play_release_testing');
|
|
expect(traceMessage(t.find(x => x.file === 'deploy/ios.sh')!)).toStartWith('altool / Transporter upload in deploy/ios.sh:1');
|
|
});
|
|
test("storebound's own checkout is not reported", () => {
|
|
expect(findFastlaneTraces(project({ 'package.json': '{"name":"storebound"}', 'scripts/x.ts': "fetch('https://api.appstoreconnect.apple.com/v1/apps')\n" }))).toEqual([]);
|
|
});
|
|
test('a project without fastlane has no traces', () => {
|
|
expect(findFastlaneTraces(project({ 'package.json': '{"scripts":{"build":"vite build"}}' }))).toEqual([]);
|
|
});
|
|
test('scan reports fastlane-upload with the evidence lines', async () => {
|
|
const dir = project({ 'Jenkinsfile': 'sh "fastlane supply"\n' });
|
|
const { report } = await runScan(dir, {});
|
|
const f = report.findings.find(x => x.rule === 'fastlane-upload' && x.store === 'play');
|
|
expect(f?.evidence[0]).toMatchObject({ file: 'Jenkinsfile', line: 1 });
|
|
expect(f?.fix).toContain('storebound import fastlane');
|
|
});
|
|
});
|
|
|
|
describe('direct store API traces stay short', () => {
|
|
test('comment lines are skipped and each file names a tool once', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'sb-fl2-'));
|
|
mkdirSync(join(dir, 'tools'));
|
|
writeFileSync(join(dir, 'tools/ship.mjs'), "/**\n * 4. `xcrun altool --upload-package` uploads it\n */\n// altool needs the app id\nconst words = ['xcrun', 'altool', '--upload-package'];\nrun(['xcrun', 'altool', '--validate-app']);\n");
|
|
expect(findFastlaneTraces(dir)).toEqual([expect.objectContaining({ file: 'tools/ship.mjs', line: 5, via: 'api' })]);
|
|
});
|
|
});
|
|
|
|
describe('test files are not traces', () => {
|
|
test('a test that quotes altool is skipped', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'sb-fl3-'));
|
|
mkdirSync(join(dir, 'tools'));
|
|
writeFileSync(join(dir, 'tools/ship.test.mjs'), "test('calls altool', () => { run(['xcrun', 'altool']); });\n");
|
|
expect(findFastlaneTraces(dir)).toEqual([]);
|
|
});
|
|
});
|