107 行
4.7 KiB
Python
107 行
4.7 KiB
Python
import importlib.util
|
|
import json
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from unittest.mock import patch
|
|
|
|
spec = importlib.util.spec_from_file_location('auto_deploy', Path(__file__).with_name('auto_deploy.py'))
|
|
deploy = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(deploy)
|
|
|
|
|
|
class DeployTest(unittest.TestCase):
|
|
def setUp(self):
|
|
self.temp = tempfile.TemporaryDirectory()
|
|
self.addCleanup(self.temp.cleanup)
|
|
self.base = Path(self.temp.name)
|
|
self.repo = self.base / 'source'
|
|
self.repo.mkdir()
|
|
self.git('init')
|
|
self.git('symbolic-ref', 'HEAD', 'refs/heads/master')
|
|
self.config = {'state': str(self.base / 'state'), 'backups': str(self.base / 'backups'),
|
|
'host': 'localhost', 'php': 'php', 'health': []}
|
|
self.target = self.base / 'site'
|
|
self.target.mkdir()
|
|
self.settings = {'name': 'backend', 'url': str(self.repo), 'target': str(self.target)}
|
|
|
|
def git(self, *args):
|
|
return subprocess.check_output(['git', '-C', str(self.repo)] + list(args), stderr=subprocess.STDOUT)
|
|
|
|
def write(self, name, value):
|
|
p = self.repo / name
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text(value)
|
|
|
|
def commit(self):
|
|
self.git('add', '-A')
|
|
self.git('-c', 'user.name=Deploy Test', '-c', 'user.email=deploy@example.invalid', 'commit', '-m', 'fixture')
|
|
|
|
def test_publish_and_repeat_without_republishing(self):
|
|
self.write('public/app.txt', 'first')
|
|
self.commit()
|
|
self.assertTrue(deploy.publish(self.config, self.settings))
|
|
self.assertEqual((self.target / 'public/app.txt').read_text(), 'first')
|
|
self.assertFalse(deploy.publish(self.config, self.settings))
|
|
|
|
def test_preserves_production_config_uploads_and_screen(self):
|
|
for name in ['application/database.php', 'application/config.php', 'application/extra/site.php',
|
|
'public/uploads/photo.jpg', 'public/screen/index.html']:
|
|
self.write(name, 'repository version')
|
|
p = self.target / name
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
p.write_text('production version')
|
|
self.write('public/app.txt', 'deploy me')
|
|
self.commit()
|
|
deploy.publish(self.config, self.settings)
|
|
self.assertEqual((self.target / 'application/database.php').read_text(), 'production version')
|
|
self.assertEqual((self.target / 'public/uploads/photo.jpg').read_text(), 'production version')
|
|
self.assertEqual((self.target / 'public/screen/index.html').read_text(), 'production version')
|
|
|
|
def test_health_failure_restores_files_and_does_not_advance_revision(self):
|
|
self.write('public/app.txt', 'first')
|
|
self.commit()
|
|
deploy.publish(self.config, self.settings)
|
|
state = (Path(self.config['state']) / 'backend.json').read_text()
|
|
self.write('public/app.txt', 'broken')
|
|
self.write('public/new.txt', 'new')
|
|
self.commit()
|
|
self.config['health'] = ['http://localhost/health']
|
|
with patch.object(deploy, 'healthcheck', side_effect=RuntimeError('unhealthy')):
|
|
with self.assertRaises(RuntimeError):
|
|
deploy.publish(self.config, self.settings)
|
|
self.assertEqual((self.target / 'public/app.txt').read_text(), 'first')
|
|
self.assertFalse((self.target / 'public/new.txt').exists())
|
|
self.assertEqual((Path(self.config['state']) / 'backend.json').read_text(), state)
|
|
|
|
def test_deletion_only_removes_previously_deployed_files(self):
|
|
self.write('public/app.txt', 'first')
|
|
self.write('public/old.txt', 'old')
|
|
self.commit()
|
|
deploy.publish(self.config, self.settings)
|
|
(self.target / 'public/unmanaged.txt').write_text('retain')
|
|
(self.repo / 'public/old.txt').unlink()
|
|
self.commit()
|
|
deploy.publish(self.config, self.settings)
|
|
self.assertFalse((self.target / 'public/old.txt').exists())
|
|
self.assertTrue((self.target / 'public/unmanaged.txt').exists())
|
|
|
|
def test_frontend_deploys_source_instead_of_stale_dist(self):
|
|
self.settings['name'] = 'frontend'
|
|
self.write('index.html', 'new screen')
|
|
self.write('dist/index.html', 'stale build')
|
|
self.write('js/app.js', 'export const value = 1;')
|
|
self.commit()
|
|
deploy.publish(self.config, self.settings)
|
|
self.assertEqual((self.target / 'index.html').read_text(), 'new screen')
|
|
self.assertFalse((self.target / 'dist').exists())
|
|
|
|
def test_rejects_paths_outside_the_site(self):
|
|
with self.assertRaises(RuntimeError):
|
|
deploy.destination(self.target, '../outside')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|