"""Render real workflow templates to verify permissions and common page structure.""" from html.parser import HTMLParser from types import SimpleNamespace from jinja2 import ChoiceLoader, DictLoader, Environment, FileSystemLoader class Elements(HTMLParser): def __init__(self, html): super().__init__() self.ids = {} self.links = [] self.fieldsets = [] self.fieldset_stack = [] self.disabled_ancestors = {} self.feed(html) def handle_starttag(self, tag, attributes): attrs = dict(attributes) if 'id' in attrs: self.ids[attrs['id']] = attrs self.disabled_ancestors[attrs['id']] = any('disabled' in parent for parent in self.fieldset_stack) if tag == 'a': self.links.append(attrs.get('href')) if tag == 'fieldset': self.fieldsets.append(attrs) self.fieldset_stack.append(attrs) def handle_endtag(self, tag): if tag == 'fieldset' and self.fieldset_stack: self.fieldset_stack.pop() def render(name, permissions=None): env = Environment(loader=ChoiceLoader([ DictLoader({'shared/frontend/base.html': '{% block extra_css %}{% endblock %}{% block content %}{% endblock %}{% block extra_js %}{% endblock %}'}), FileSystemLoader('app'), ]), autoescape=True) return env.get_template('workflows/frontend/'+name+'.html').render( workflow_permissions=permissions or {}, time_id=12, request=SimpleNamespace(url=SimpleNamespace(path='/workflows'))) def test_viewer_can_browse_without_write_controls_or_admin_links(): html = render('builder', {'view': True}) page = Elements(html) assert 'disabled' in page.fieldsets[0] for key in ('new', 'save', 'copy', 'disable', 'test', 'activate'): assert 'hidden' in page.ids['wf-'+key] assert '/workflows/operations' in page.links assert '/workflows/resources' not in page.links assert '/tags/classification' not in page.links assert 'Henter workflows' in html def test_manage_and_activation_are_separate_from_system_admin_navigation(): html = render('builder', {'view': True, 'manage': True, 'activate': True}) page = Elements(html) assert 'disabled' not in page.fieldsets[0] assert 'hidden' not in page.ids['wf-save'] assert 'hidden' not in page.ids['wf-activate'] assert '/workflows/resources' in page.links assert '/tags/classification' not in page.links assert '/tags/classification' in Elements(render('builder', {'system_admin': True})).links def test_override_does_not_expose_reset_without_activation_permission(): page = Elements(render('decision', {'override': True})) assert 'hidden' in page.ids['decision-reset'] assert 'hidden' not in Elements(render('decision', {'override': True, 'activate': True})).ids['decision-reset'] def test_activation_only_user_can_deactivate_without_editing_definition(): page = Elements(render('builder', {'view': True, 'activate': True})) assert 'hidden' not in page.ids['wf-disable'] assert not page.disabled_ancestors['wf-disable'] assert 'hidden' in page.ids['wf-save'] def test_ops_permission_flags_are_rendered_as_booleans_for_dynamic_buttons(): readonly = render('operations', {'view': True}) assert 'const canManage=false,canExport=false;' in readonly assert 'const canManage=true,canExport=true;' in render('operations', {'manage': True, 'economic_export': True}) def test_pages_share_theme_tokens_and_responsive_scope(): for name in ('builder', 'decision', 'resources', 'operations'): html = render(name, {'view': True}) assert 'container-fluid workflow-page' in html assert 'workflow-heading' in html assert 'var(--bg-card' in html assert 'var(--text-secondary' in html assert '@media(max-width:576px)' in html