← 메인 화면프로젝트 자료·코드
← 자료 목록

cc/tests/test_materials.py

프로그램·코드 · 4.0 KB원본 다운로드

원본 내용을 읽기 전용으로 표시합니다.

  1. import hashlib
  2. import json
  3. from pathlib import Path
  4. import pytest
  5. from startup_app.app import create_app
  6. from tools.publish_materials import publish
  7. @pytest.fixture
  8. def materials_client(tmp_path):
  9. files = tmp_path / 'public' / 'files'
  10. files.mkdir(parents=True)
  11. content = [('example.py', 'code', '<script>alert("x")</script>\nprint("안녕")'),
  12. ('data.csv', 'csv', '이름,값\n<script>,2\n'), ('map.html', 'map', '<html>map</html>'),
  13. ('plot.png', 'image', 'image')]
  14. entries = []
  15. for i, (name, kind, text) in enumerate(content):
  16. (files / name).write_text(text, encoding='utf-8')
  17. entries.append({'id': str(i) * 20, 'path': name, 'kind': kind, 'size': len(text.encode()), 'sha256': 'example', 'rows': 1, 'columns': 2})
  18. (tmp_path / 'outside.txt').write_text('not-public', encoding='utf-8')
  19. entries.append({'id': 'f' * 20, 'path': '../../outside.txt', 'kind': 'text', 'size': 10, 'sha256': 'example'})
  20. (tmp_path / 'public' / 'manifest.json').write_text(json.dumps({'created': 'today', 'files': entries}), encoding='utf-8')
  21. app = create_app()
  22. app.config['MATERIALS_DIR'] = tmp_path / 'public'
  23. return app.test_client()
  24. def test_index_search_and_no_arbitrary_directory(materials_client):
  25. assert 'example.py' in materials_client.get('/materials/').text
  26. filtered = materials_client.get('/materials/?q=example').text
  27. assert 'example.py' in filtered and 'plot.png' not in filtered
  28. assert materials_client.get('/materials/?folder=../../').status_code == 404
  29. assert materials_client.get('/materials/download/.env').status_code == 404
  30. assert materials_client.get('/materials/download/' + 'f' * 20).status_code == 404
  31. def test_source_and_csv_are_escaped(materials_client):
  32. source = materials_client.get('/materials/file/' + '0' * 20)
  33. assert '&lt;script&gt;' in source.text and '<script>alert' not in source.text
  34. data = materials_client.get('/materials/file/' + '1' * 20)
  35. assert '&lt;script&gt;' in data.text and '<script>' not in data.text
  36. def test_download_and_preview_isolation(materials_client):
  37. download = materials_client.get('/materials/download/' + '0' * 20)
  38. assert download.status_code == 200 and 'attachment' in download.headers['Content-Disposition']
  39. frame = materials_client.get('/materials/file/' + '2' * 20)
  40. assert 'sandbox="allow-scripts"' in frame.text
  41. preview = materials_client.get('/materials/preview/' + '2' * 20)
  42. assert preview.status_code == 200 and 'sandbox allow-scripts;' in preview.headers['Content-Security-Policy']
  43. assert 'allow-same-origin' not in preview.headers['Content-Security-Policy']
  44. assert materials_client.get('/materials/preview/' + '0' * 20).status_code == 404
  45. assert materials_client.get('/materials/image/' + '0' * 20).status_code == 404
  46. assert 'frame-src' in frame.headers['Content-Security-Policy']
  47. def test_export_excludes_private_folders_and_stops_on_secret(tmp_path):
  48. root, output = tmp_path / 'project', tmp_path / 'public'
  49. for folder in ('data', 'output', 'cc/startup_app', 'cc/tests', 'cc/tools', 'cc/.venv'):
  50. (root / folder).mkdir(parents=True)
  51. (root / 'app.py').write_text('print("hello")', encoding='utf-8')
  52. (root / 'cc' / '.venv' / 'private.py').write_text('private', encoding='utf-8')
  53. (root / '.env').write_text('private', encoding='utf-8')
  54. for name in ('requirements.txt', 'requirements-dev.txt'):
  55. (root / 'cc' / name).write_text('package', encoding='utf-8')
  56. publish(root, output)
  57. manifest = json.loads((output / 'manifest.json').read_text(encoding='utf-8'))
  58. assert len(manifest['files']) == 3
  59. assert all('.env' not in f['path'] and '.venv' not in f['path'] for f in manifest['files'])
  60. for entry in manifest['files']:
  61. assert hashlib.sha256((output / 'files' / entry['path']).read_bytes()).hexdigest() == entry['sha256']
  62. (root / 'app.py').write_text('token = "' + 'sk-' + 'x' * 40 + '"', encoding='utf-8')
  63. with pytest.raises(ValueError, match='공개 중단'):
  64. publish(root, output)
파일 확인 정보

SHA-256: f815c2432d6cb0b66fdecc1a9d6d7fa17440e643eb3b8612c45af7544f5efc1a