async def test_post_parse(self) -> None: event = Event('E0', Birth()) with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.extensions.add(ExtensionConfiguration(Cleaner)) async with App(configuration) as app: app.ancestry.entities.append(event) await load(app) self.assertEquals([], list(app.ancestry.entities[Event]))
async def _parse(self, xml: str) -> Ancestry: with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') async with Site(configuration) as site: with NamedTemporaryFile(mode='r+') as f: f.write(xml.strip()) f.seek(0) parse_xml(site, f.name) return site.ancestry
async def test_post_parse(self): person = Person('P0') reference_presence = Presence(person, Subject(), Event(Residence())) reference_presence.event.date = Date(1970, 1, 1) with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.extensions[Deriver] = None async with App(configuration) as app: app.ancestry.people[person.id] = person await load(app) self.assertEquals(3, len(person.presences)) self.assertEquals( DateRange(None, Date(1970, 1, 1), end_is_boundary=True), person.start.date) self.assertEquals(DateRange(Date(1970, 1, 1), start_is_boundary=True), person.end.date)
def test_post_render_config_with_content_negotiation(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration( output_directory_path, 'http://example.com') configuration.content_negotiation = True configuration.plugins[Nginx] = {} site = Site(configuration) render(site) expected = '''server { listen 80; server_name example.com; root %s; add_header Cache-Control "max-age=86400"; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_types text/css application/javascript application/json application/xml; set_by_lua_block $content_type_extension { local available_content_types = {'text/html', 'application/json'} local content_type_extensions = {} content_type_extensions['text/html'] = 'html' content_type_extensions['application/json'] = 'json' local content_type = require('cone').negotiate(ngx.req.get_headers()['Accept'], available_content_types) return content_type_extensions[content_type] } index index.$content_type_extension; location / { # Handle HTTP error responses. error_page 401 /.error/401.$content_type_extension; error_page 403 /.error/403.$content_type_extension; error_page 404 /.error/404.$content_type_extension; location /.error { internal; } try_files $uri $uri/ =404; } }''' % configuration.www_directory_path # noqa: E101 W191 with open(join(configuration.output_directory_path, 'nginx', 'nginx.conf')) as f: # noqa: E101 self.assertEquals(expected, f.read())
async def setUpClass(cls) -> None: TestCase.setUpClass() # @todo Convert each test method to use self._load(), so we can remove this shared XML file. with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') async with App(configuration) as app: cls.ancestry = app.ancestry xml_file_path = Path(__file__).parent / 'assets' / 'data.xml' with open(xml_file_path) as f: load_xml(app.ancestry, f.read(), rootname(xml_file_path))
def test_generate_multilingual(self): configuration = Configuration('/tmp', 'https://example.com') configuration.locales.replace([ LocaleConfiguration('nl'), LocaleConfiguration('en'), ]) sut = LocalizedPathUrlGenerator(configuration) self.assertEquals('/nl/index.html', sut.generate('/index.html', 'text/html')) self.assertEquals( '/en/index.html', sut.generate('/index.html', 'text/html', locale='en'))
async def test_load(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.extensions.add(ExtensionConfiguration(Demo)) async with App(configuration) as app: await load(app) self.assertNotEqual(0, len(app.ancestry.entities[Person])) self.assertNotEqual(0, len(app.ancestry.entities[Place])) self.assertNotEqual(0, len(app.ancestry.entities[Event])) self.assertNotEqual(0, len(app.ancestry.entities[Source])) self.assertNotEqual(0, len(app.ancestry.entities[Citation]))
async def test_generate_window_close_button_should_close_window(assert_not_window, navigate, qtbot, tmpdir) -> None: configuration = Configuration(tmpdir, 'https://example.com') async with App(configuration) as app: sut = _GenerateWindow(app) qtbot.addWidget(sut) with qtbot.waitSignal(sut._thread.finished): sut.show() qtbot.mouseClick(sut._close_button, QtCore.Qt.LeftButton) assert_not_window(_GenerateWindow)
async def test_citation(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') app = App(configuration) citation = Citation('CITATION1', Source('A Little Birdie')) app.ancestry.entities.append(citation) async with app: await generate(app) self.assert_betty_html(app, '/citation/%s/index.html' % citation.id) self.assert_betty_json(app, '/citation/%s/index.json' % citation.id, 'citation')
def test_person_with_individual_and_affiliation_names( self, expected: str, locale: str): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.locales['en-US'] = LocaleConfiguration('en-US', 'en') configuration.locales['nl-NL'] = LocaleConfiguration('nl-NL', 'nl') site = Site(configuration) environment = create_environment(site, locale) person_id = 'P1' individual_name = 'Jane' affiliation_name = 'Doughnut' person = Person(person_id) person.names.append(PersonName(individual_name, affiliation_name)) site.ancestry.people[person_id] = person indexed = list(index(site, environment)) self.assertEquals('jane doughnut', indexed[0]['text']) self.assertIn(expected, indexed[0]['result'])
async def setUpClass(cls) -> None: TestCase.setUpClass() # @todo Convert each test method to use self._parse(), so we can remove this shared XML file. with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') async with Site(configuration) as site: cls.ancestry = site.ancestry parse_xml( site, join(dirname(abspath(__file__)), 'assets', 'data.xml'))
async def test_source(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') app = App(configuration) source = Source('SOURCE1', 'A Little Birdie') app.ancestry.entities.append(source) async with app: await generate(app) self.assert_betty_html(app, '/source/%s/index.html' % source.id) self.assert_betty_json(app, '/source/%s/index.json' % source.id, 'source')
async def test_person(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') app = App(configuration) async with app: person = Person('PERSON1') app.ancestry.entities.append(person) await generate(app) self.assert_betty_html(app, '/person/%s/index.html' % person.id) self.assert_betty_json(app, '/person/%s/index.json' % person.id, 'person')
def test(self, expected, template, file): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') environment = create_environment(Site(configuration)) actual = environment.from_string(template).render(file=file) self.assertEquals(expected, actual) for file_path in actual.split(':'): self.assertTrue( exists( join(configuration.www_directory_path, file_path[1:])))
async def _render( self, data: Optional[Dict] = None, template_file: Optional[Template] = None, template_string: Optional[str] = None, update_configuration: Optional[Callable[[Configuration], None]] = None ) -> Tuple[str, App]: if template_string is not None and template_file is not None: raise RuntimeError( 'You must define either `template_string` or `template_file`, but not both.' ) if template_string is not None: template = template_string template_factory = Environment.from_string elif template_file is not None: template = template_file template_factory = Environment.get_template elif self.template_string is not None: template = self.template_string template_factory = Environment.from_string elif self.template_file is not None: template = self.template_file template_factory = Environment.get_template else: class_name = self.__class__.__name__ raise RuntimeError( f'You must define one of `template_string`, `template_file`, `{class_name}.template_string`, or `{class_name}.template_file`.' ) if data is None: data = {} with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.debug = True if update_configuration is not None: update_configuration(configuration) async with App(configuration) as app: rendered = template_factory(app.jinja2_environment, template).render(**data) await app.wait() yield rendered, app
async def test_generate_window_serve_button_should_open_serve_window(assert_window, mocker, navigate, qtbot, tmpdir) -> None: mocker.patch('webbrowser.open_new_tab') configuration = Configuration(tmpdir, 'https://example.com') async with App(configuration) as app: sut = _GenerateWindow(app) qtbot.addWidget(sut) with qtbot.waitSignal(sut._thread.finished): sut.show() qtbot.mouseClick(sut._serve_button, QtCore.Qt.LeftButton) assert_window(_ServeAppWindow)
async def test_file(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') app = App(configuration) async with app: with NamedTemporaryFile() as f: file = File('FILE1', Path(f.name)) app.ancestry.entities.append(file) await generate(app) self.assert_betty_html(app, '/file/%s/index.html' % file.id) self.assert_betty_json(app, '/file/%s/index.json' % file.id, 'file')
async def test_root_redirect(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') configuration.locales.replace([ LocaleConfiguration('nl'), LocaleConfiguration('en'), ]) async with App(configuration) as app: await generate(app) with open(self.assert_betty_html(app, '/index.html')) as f: meta_redirect = '<meta http-equiv="refresh" content="0; url=/nl/index.html">' self.assertIn(meta_redirect, f.read())
async def test_post_parse(self) -> None: person = Person('P0') person.private = True PersonName(person, 'Jane', 'Dough') with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.extensions.add(ExtensionConfiguration(Anonymizer)) async with App(configuration) as app: app.ancestry.entities.append(person) await load(app) self.assertEquals(0, len(person.names))
async def test_empty(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.locales.replace([ LocaleConfiguration('en-US', 'en'), LocaleConfiguration('nl-NL', 'nl'), ]) async with App(configuration) as app: indexed = [item for item in Index(app).build()] self.assertEquals([], indexed)
async def test_extensions_with_comes_after_without_other_extension( self) -> None: configuration = Configuration(**self._MINIMAL_CONFIGURATION_ARGS) configuration.extensions.add( ExtensionConfiguration( ComesAfterNonConfigurableExtensionExtension)) async with App(configuration) as sut: carrier = [] await sut.dispatcher.dispatch(Tracker, 'track')(carrier) self.assertEquals(1, len(carrier)) self.assertEquals(ComesAfterNonConfigurableExtensionExtension, type(carrier[0]))
def test_post_render_config_with_https(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration( output_directory_path, 'https://example.com') configuration.plugins[Nginx] = {} site = Site(configuration) render(site) expected = ''' server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name example.com; root %s; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Cache-Control "max-age=86400"; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_types text/css application/javascript application/json application/xml; set $content_type_extension html; index index.$content_type_extension; location / { # Handle HTTP error responses. error_page 401 /.error/401.$content_type_extension; error_page 403 /.error/403.$content_type_extension; error_page 404 /.error/404.$content_type_extension; location /.error { internal; } try_files $uri $uri/ =404; } }''' % configuration.www_directory_path # noqa: E101 W191 with open(join(configuration.output_directory_path, 'nginx', 'nginx.conf')) as f: # noqa: E101 self.assertEquals(expected, f.read())
async def test_post_render_config_with_content_negotiation(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'http://example.com') configuration.content_negotiation = True configuration.plugins[Nginx] = {} expected = r''' server { listen 80; server_name example.com; root %s; add_header Cache-Control "max-age=86400"; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_types text/css application/javascript application/json application/xml; set_by_lua_block $media_type_extension { local available_media_types = {'text/html', 'application/json'} local media_type_extensions = {} media_type_extensions['text/html'] = 'html' media_type_extensions['application/json'] = 'json' local media_type = require('cone').negotiate(ngx.req.get_headers()['Accept'], available_media_types) return media_type_extensions[media_type] } index index.$media_type_extension; location / { # Handle HTTP error responses. error_page 401 /.error/401.$media_type_extension; error_page 403 /.error/403.$media_type_extension; error_page 404 /.error/404.$media_type_extension; location /.error { internal; } try_files $uri $uri/ =404; } }''' % configuration.www_directory_path await self._assert_configuration_equals(expected, configuration)
async def test_post_render_config_with_overridden_www_directory_path(self): with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.extensions[Nginx] = Nginx.configuration_schema( {'www_directory_path': '/tmp/overridden-www'}) expected = r''' server { listen 80; server_name example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name example.com; root /tmp/overridden-www; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header Cache-Control "max-age=86400"; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_types text/css application/javascript application/json application/xml; set $media_type_extension html; index index.$media_type_extension; location / { # Handle HTTP error responses. error_page 401 /.error/401.$media_type_extension; error_page 403 /.error/403.$media_type_extension; error_page 404 /.error/404.$media_type_extension; location /.error { internal; } try_files $uri $uri/ =404; } } ''' await self._assert_configuration_equals(expected, configuration)
def test_post_render_config_without_clean_urls(self): self.maxDiff = None with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://example.com') configuration.plugins[Nginx] = {} site = Site(configuration) render(site) expected = '''server { # The port to listen to. listen 80; # The publicly visible hostname. server_name example.com; # The path to the local web root. root %s; # The cache lifetime. add_header Cache-Control "max-age=86400"; # Handle HTTP error responses. error_page 401 /.error/401.html; error_page 403 /.error/403.html; error_page 404 /.error/404.html; location /.error { internal; } # When directories are requested, serve their index.html contents. location / { if ($request_method = OPTIONS) { add_header Allow "OPTIONS, GET"; return 200; } index index.html; try_files $uri $uri/ =404; } }''' % configuration.www_directory_path # noqa: E101 W191 with open(join(configuration.output_directory_path, 'nginx.conf')) as f: # noqa: E101 self.assertEquals(expected, f.read())
async def test_validate(self): configuration = Configuration(self._output_directory.name, 'https://ancestry.example.com') async with App(configuration) as app: await generate(app) with open( Path(__file__).parent / 'test_generate_assets' / 'sitemap.xsd') as f: schema_doc = etree.parse(f) schema = etree.XMLSchema(schema_doc) with open(app.configuration.www_directory_path / 'sitemap.xml') as f: sitemap_doc = etree.parse(f) schema.validate(sitemap_doc)
async def test_extensions_with_one_configurable_extension(self) -> None: configuration = Configuration(**self._MINIMAL_CONFIGURATION_ARGS) check = 1337 configuration.extensions.add( ExtensionConfiguration( ConfigurableExtension, True, ConfigurableExtensionConfiguration(check=check, ))) async with App(configuration) as sut: self.assertIsInstance(sut.extensions[ConfigurableExtension], ConfigurableExtension) self.assertEquals( check, sut.extensions[ConfigurableExtension]._configuration.check)
async def test_render_file_should_ignore_non_sass_or_scss(self) -> None: with TemporaryDirectory() as output_directory_path: configuration = Configuration(output_directory_path, 'https://ancestry.example.com') async with Site(configuration) as site: sut = Jinja2Renderer(site.jinja2_environment, configuration) template = '{% if true %}true{% endif %}' with TemporaryDirectory() as working_directory_path: template_file_path = path.join(working_directory_path, 'betty.txt') with open(template_file_path, 'w') as f: f.write(template) await sut.render_file(template_file_path) with open(path.join(working_directory_path, 'betty.txt')) as f: self.assertEquals(template, f.read())
def new_project(self) -> None: configuration_file_path, _ = QFileDialog.getSaveFileName( self, 'Save your new project to...', '', _CONFIGURATION_FILE_FILTER) if not configuration_file_path: return configuration = Configuration( path.join(path.dirname(configuration_file_path), 'output'), 'https://example.com') with open(configuration_file_path, 'w') as f: to_file(f, configuration) project_window = ProjectWindow(configuration_file_path) project_window.show() self.close()
async def start(self) -> None: self._output_directory = TemporaryDirectory() configuration = Configuration(self._output_directory.name, 'https://example.com') configuration.extensions[Demo] = None # The Nginx extension allows content negotiation if Docker is also available. configuration.extensions[Nginx] = {} # Include all of the translations Betty ships with. locale_configurations = [ LocaleConfiguration('en-US', 'en'), LocaleConfiguration('nl-NL', 'nl'), LocaleConfiguration('fr-FR', 'fr'), LocaleConfiguration('uk', 'uk'), ] for locale_configuration in locale_configurations: configuration.locales[ locale_configuration.locale] = locale_configuration self._app = App(configuration) self._server = None await self._app.enter() await load.load(self._app) await generate.generate(self._app) self._server = serve.AppServer(self._app) await self._server.start()