diff --git a/README.md b/README.md index 670ad80..a0e5085 100644 --- a/README.md +++ b/README.md @@ -276,6 +276,9 @@ Lints all packaging configurations and metadata: - `appstreamcli validate` - Flatpak manifest consistency checks - FreeBSD dry run & embedded icons verification +- AppImage desktop integration and bundled CLI/MCP tests, when the caller provides + `packaging/linux/apprun-test.sh` or `packaging/linux/apprun-cli-test.sh`. No app-local + workflow job is needed; a failing script fails the shared lint job. #### Caller Example: ```yaml diff --git a/actions/packaging-lint/action.yml b/actions/packaging-lint/action.yml index 17d0437..1067f11 100644 --- a/actions/packaging-lint/action.yml +++ b/actions/packaging-lint/action.yml @@ -67,6 +67,13 @@ runs: bash packaging/linux/apprun-test.sh fi + - name: AppImage bundled CLI and MCP entry point + shell: bash + run: | + if [ -f packaging/linux/apprun-cli-test.sh ]; then + bash packaging/linux/apprun-cli-test.sh + fi + - name: Linux tarball installer test shell: bash run: | diff --git a/tests/test_packaging_cli.py b/tests/test_packaging_cli.py new file mode 100644 index 0000000..cc94256 --- /dev/null +++ b/tests/test_packaging_cli.py @@ -0,0 +1,44 @@ +"""Exercise the shared AppImage CLI hook without building an app.""" +from pathlib import Path +import subprocess +import tempfile +import unittest +import yaml + +ROOT = Path(__file__).resolve().parents[1] +ACTION = yaml.safe_load((ROOT / 'actions/packaging-lint/action.yml').read_text()) +STEP = next(s for s in ACTION['runs']['steps'] if s['name'] == 'AppImage bundled CLI and MCP entry point') + + +class PackagingCliTests(unittest.TestCase): + def run_hook(self, script=None): + with tempfile.TemporaryDirectory(prefix='packaging hook ') as directory: + root = Path(directory) + if script is not None: + target = root / 'packaging/linux/apprun-cli-test.sh' + target.parent.mkdir(parents=True) + target.write_text(script) + result = subprocess.run(['bash', '-e', '-o', 'pipefail', '-c', STEP['run']], + cwd=root, capture_output=True, text=True, timeout=10) + ran = (root / 'ran').exists() + return result, ran + + def test_missing_hook_is_optional(self): + result, ran = self.run_hook() + self.assertEqual(result.returncode, 0, result.stderr) + self.assertFalse(ran) + + def test_hook_runs_in_the_caller_checkout_without_executable_bit(self): + result, ran = self.run_hook('printf "CLI hook ran\\n"; touch ran\n') + self.assertEqual(result.returncode, 0, result.stderr) + self.assertTrue(ran) + self.assertIn('CLI hook ran', result.stdout) + + def test_failure_is_not_swallowed(self): + result, _ = self.run_hook('echo "CLI regression" >&2; exit 37\n') + self.assertEqual(result.returncode, 37) + self.assertIn('CLI regression', result.stderr) + + +if __name__ == '__main__': + unittest.main()