Unverified Commit e1c1c993 by Johannes vom Dorp Committed by GitHub

Merge pull request #1 from fkie-cad/beta

Beta version of report generator
parents f7067821 908d4702
.project
.pydevproject
.idea
# Byte-compiled / optimized / DLL files
__pycache__/
......
[settings]
line_length=120
known_first_party=test
multi_line_output=6
default_stages: [commit, push]
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.1.0
hooks:
- id: check-added-large-files
args: [--maxkb=10000]
- id: check-json
- id: check-merge-conflict
- id: check-yaml
- id: end-of-file-fixer
types: [python]
- id: fix-encoding-pragma
args: [--remove]
- id: file-contents-sorter
files: src/unpacker/passwords|.gitignore|_list.txt
- id: flake8
args: [--ignore="E501,W503", --select=W504]
- id: forbid-new-submodules
- id: no-commit-to-branch
- id: pretty-format-json
args: [--autofix]
- id: trailing-whitespace
types: [python]
- repo: https://github.com/pre-commit/mirrors-pylint
rev: v2.2.2
hooks:
- id: pylint
language: system
args: [--rcfile=.pylintrc]
- repo: https://github.com/pre-commit/mirrors-jshint
rev: v2.10.2
hooks:
- id: jshint
- repo: https://github.com/pre-commit/mirrors-isort
rev: v4.3.20
hooks:
- id: isort
This diff is collapsed. Click to expand it.
language: python
python:
- "3.5"
- "3.6"
# command to install dependencies
install:
- "pip install -r requirements.txt"
- "pip install codecov"
# command to run tests
script: "pytest"
after_success:
- codecov
\ No newline at end of file
FROM phusion/baseimage:0.11
WORKDIR /opt/app
COPY . /opt/app
RUN install_clean git python3 python3-pip python3-wheel python3-setuptools texlive-latex-base texlive-latex-extra lmodern
RUN pip3 install -r requirements.txt
RUN apt-get remove -y python3-pip
ENTRYPOINT ["./docker_entry.py"]
#!/usr/bin/env python3
'''
fact_pdf_report
Copyright (C) 2015-2019 Fraunhofer FKIE
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
'''
import json
import shutil
from pathlib import Path
from tempfile import TemporaryDirectory
from pdf_generator.generator import compile_pdf, create_templates
def get_data():
return json.loads(Path('/tmp', 'interface', 'data', 'analysis.json').read_text()), json.loads(Path('/tmp', 'interface', 'data', 'meta.json').read_text())
def move_pdf_report(pdf_path):
shutil.move(str(pdf_path.absolute()), str(Path('/tmp', 'interface', 'pdf', pdf_path.name)))
def main():
analysis, meta_data = get_data()
with TemporaryDirectory() as tmp_dir:
create_templates(analysis, meta_data, tmp_dir)
target_path = compile_pdf(meta_data, tmp_dir)
move_pdf_report(target_path)
return 0
if __name__ == '__main__':
exit(main())
import os
import shutil
from pathlib import Path
from common_helper_process import execute_shell_command
from pdf_generator.tex_generation.template_engine import (
LOGO_FILE, MAIN_TEMPLATE, META_TEMPLATE, PLUGIN_TEMPLATE_BLUEPRINT, TemplateEngine
)
PDF_NAME = Path(MAIN_TEMPLATE).with_suffix('.pdf').name
def execute_latex(tmp_dir):
current_dir = os.getcwd()
os.chdir(tmp_dir)
execute_shell_command('env buf_size=1000000 pdflatex {}'.format(MAIN_TEMPLATE))
os.chdir(current_dir)
def copy_fact_image(target):
shutil.copy(str(Path(__file__).parent / 'templates' / LOGO_FILE), str(Path(target) / LOGO_FILE))
def render_analysis_templates(engine, analysis):
return [
(PLUGIN_TEMPLATE_BLUEPRINT.format(analysis_plugin), engine.render_analysis_template(analysis_plugin, analysis[analysis_plugin])) for analysis_plugin in analysis
]
def create_report_filename(meta_data):
unsafe_name = '{}_analysis_report.pdf'.format(meta_data['device_name'])
safer_name = unsafe_name.replace(' ', '_').replace('/', '__')
return safer_name.encode('latin-1', errors='ignore').decode('latin-1')
def compile_pdf(meta_data, tmp_dir):
copy_fact_image(tmp_dir)
execute_latex(tmp_dir)
target_path = Path(tmp_dir, create_report_filename(meta_data))
shutil.move(str(Path(tmp_dir, PDF_NAME)), str(target_path))
return target_path
def create_templates(analysis, meta_data, tmp_dir):
engine = TemplateEngine(tmp_dir=tmp_dir)
Path(tmp_dir, MAIN_TEMPLATE).write_text(engine.render_main_template(analysis=analysis, meta_data=meta_data))
Path(tmp_dir, META_TEMPLATE).write_text(engine.render_meta_template(meta_data))
for filename, rendered_template in render_analysis_templates(engine=engine, analysis=analysis):
Path(tmp_dir, filename).write_text(rendered_template)
\subsection*{Binwalk (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
\multicolumn{2}{|p{14.5cm}|}{Signature Analysis:} \\
\multicolumn{2}{|p{14.5cm}|}{} \\
\multicolumn{2}{|p{14.5cm}|}{\VAR{selected_analysis['signature_analysis'] | filter_chars}} \\
\hline
Entropy Graph & \includegraphics[scale = 0.7]{\VAR{selected_analysis['entropy_analysis_graph'] | base64_to_png('entropy_analysis_graph', tmp_dir)}} \\
\hline
\BLOCK{if selected_analysis['summary']}
\multicolumn{2}{|p{14.5cm}|}{Summary:} \\
\multicolumn{2}{|p{14.5cm}|}{} \\
\BLOCK{for data in selected_analysis['summary']}
\multicolumn{2}{|p{14.5cm}|}{\VAR{data | filter_chars}} \\
\BLOCK{endfor}
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\subsection*{Exploit Mitigation (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
\BLOCK{if selected_analysis['summary']}
NX
&
\BLOCK{for selected_summary in selected_analysis['summary']}
\BLOCK{if selected_summary | contains('NX')}
\VAR{selected_summary | filter_chars} (\VAR{ selected_analysis['summary'][selected_summary] | elements_count })
\BLOCK{endif}
\BLOCK{endfor} \\
\hline
Canary
&
\BLOCK{for selected_summary in selected_analysis['summary']}
\BLOCK{if selected_summary | contains('Canary')}
\VAR{selected_summary | filter_chars} (\VAR{ selected_analysis['summary'][selected_summary] | elements_count })
\BLOCK{endif}
\BLOCK{endfor} \\
\hline
PIE
&
\BLOCK{for selected_summary in selected_analysis['summary']}
\BLOCK{if selected_summary | contains('PIE')}
\VAR{selected_summary | filter_chars} (\VAR{ selected_analysis['summary'][selected_summary] | elements_count })
\BLOCK{endif}
\BLOCK{endfor} \\
\hline
RELRO
&
\BLOCK{for selected_summary in selected_analysis['summary']}
\BLOCK{if selected_summary | contains('RELRO')}
\VAR{selected_summary | filter_chars} (\VAR{ selected_analysis['summary'][selected_summary] | elements_count })
\BLOCK{endif}
\BLOCK{endfor} \\
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\subsection*{Hashes (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{tabular}{|p{3cm}|p{11.5cm}|}
\hline
md5 & \VAR{selected_analysis['md5']}\\
\hline
ripemd160 & \VAR{selected_analysis['ripemd160']}\\
\hline
sha1 & \VAR{selected_analysis['sha1']}\\
\hline
sha256 & \VAR{selected_analysis['sha256']}\\
\hline
sha512 & \VAR{selected_analysis['sha512'] | split_hash}\\
\hline
\end{tabular}
}
\ No newline at end of file
\subsection*{File Type (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
File Type & \VAR{selected_analysis['full'] | filter_chars} \\
\hline
MIME & \VAR{selected_analysis['mime'] | filter_chars} \\
\hline
\BLOCK{if selected_analysis['summary']}
Containing Files
\BLOCK{for selected_summary in selected_analysis['summary']}
& \VAR{ selected_summary | filter_chars } (\VAR{ selected_analysis['summary'][selected_summary] | elements_count }) \\
\BLOCK{endfor}
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\subsection*{\VAR{plugin_name | plugin_name | filter_chars} (v. \VAR{selected_analysis['plugin_version'] | filter_chars})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
Time of Analysis & \VAR{selected_analysis['analysis_date'] | nice_unix_time} \\
\hline
Plugin Version & \VAR{selected_analysis['plugin_version']} \\
\hline
\BLOCK{if selected_analysis['summary']}
Summary
\BLOCK{for selected_summary in selected_analysis['summary']}
& \VAR{selected_summary | filter_chars} \\
\BLOCK{endfor}
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\subsection*{IPs and URIs (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
IPs v4
\BLOCK{for ip in selected_analysis['ips_v4'] | check_list | filter_list}
& \VAR{ip} \\
\BLOCK{endfor}
\hline
IPs v6
\BLOCK{for ip in selected_analysis['ips_v6'] | check_list | filter_list}
& \VAR{ip} \\
\BLOCK{endfor}
\hline
URIs
\BLOCK{for uri in selected_analysis['uris'] | check_list | filter_list}
& \VAR{uri} \\
\BLOCK{endfor}
\hline
\BLOCK{if selected_analysis['summary']}
\multicolumn{2}{|p{14.5cm}|}{Summary:} \\
\multicolumn{2}{|p{14.5cm}|}{} \\
\BLOCK{for data in selected_analysis['summary']}
\multicolumn{2}{|p{14.5cm}|}{\VAR{data | filter_chars}} \\
\BLOCK{endfor}
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\documentclass{article}
\usepackage[english]{babel}
\usepackage[utf8]{inputenc}
\usepackage{amsmath}
\usepackage{graphicx}
\usepackage{lmodern}
\usepackage[a4paper, total={6in, 8in}]{geometry}
\usepackage{longtable}
\usepackage{sectsty}
\allsectionsfont{\sffamily}
\begin{document}
\begin{titlepage}
\newcommand{\HRule}{\rule{\linewidth}{0.5mm}}
\center
\includegraphics[width = 13cm]{fact_logo.png}\\[3cm]
\HRule \\[0.4cm]
{\fontfamily{lmss}\selectfont
{\Large \bfseries Firmware Analysis of \VAR{meta_data['hid'] | filter_chars }}\\[0.4cm]
}
\HRule \\[1.5cm]
\vspace{3.0cm}
{\fontfamily{lmss}\selectfont
\input{meta.tex}
}
\vfill
\end{titlepage}
\BLOCK{for current_analysis in analysis}
\input{\VAR{current_analysis}.tex}
\newpage
\BLOCK{endfor}
\end{document}
\subsection*{Malware (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
MD5 & \VAR{selected_analysis['md5']} \\
\hline
System Version & \VAR{selected_analysis['system_version']} \\
\hline
Scanners Number & \VAR{selected_analysis['number_of_scanners']} \\
\hline
Positives & \VAR{selected_analysis['positives']} \\
\hline
Scanners
\BLOCK{for scanner in selected_analysis['scanners']}
& \VAR{scanner} \\
\BLOCK{endfor}
\hline
Scanns
\BLOCK{for scan in selected_analysis['scans']}
\BLOCK{for value in selected_analysis['scans'][scan]}
& \VAR{scan} :: \VAR{value}:\VAR{selected_analysis['scans'][scan][value]} \\
\BLOCK{endfor}
\BLOCK{endfor}
\hline
\BLOCK{if selected_analysis['summary']}
Summary
\BLOCK{for selected_summary in selected_analysis['summary']}
& \VAR{selected_summary | filter_chars} \\
\BLOCK{endfor}
\hline
\BLOCK{endif}
\end{longtable}
}
\ No newline at end of file
\begin{tabular}{|p{3cm}|p{11.5cm}|}
\hline
HID & \VAR{meta_data['hid'] | filter_chars}\\
\hline
Device Name & \VAR{meta_data['device_name'] | filter_chars}\\
\hline
Vendor & \VAR{meta_data['vendor'] | filter_chars}\\
\hline
Device Class & \VAR{meta_data['device_class'] | filter_chars}\\
\hline
Version & \VAR{meta_data['version'] | filter_chars}\\
\hline
Release Date & \VAR{meta_data['release_date']}\\
\hline
Size & \VAR{meta_data['size'] | number_format}\\
\hline
\end{tabular}
\ No newline at end of file
\subsection*{String Stats (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{tabular}{|p{3cm}|p{11.5cm}|}
\hline
String Count & \VAR{selected_analysis['strings'] | elements_count}\\
\hline
\end{tabular}
}
\ No newline at end of file
\subsection*{String Eval Stats (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{tabular}{|p{3cm}|p{11.5cm}|}
\hline
String Count & \VAR{selected_analysis['string_eval'] | elements_count}\\
\hline
\end{tabular}
}
\ No newline at end of file
\subsection*{Unpacker (v. \VAR{selected_analysis['plugin_version']})}
{\fontfamily{lmss}\selectfont
\begin{longtable}{|p{3cm}|p{11.5cm}|}
\hline
Plugin & \VAR{selected_analysis['plugin_used'] | filter_chars} \\
\hline
Extracted & \VAR{selected_analysis['number_of_unpacked_files']} \\
\hline
\BLOCK{if selected_analysis['output']}
\multicolumn{2}{|p{14.5cm}|}{Output:}\\
\multicolumn{2}{|p{14.5cm}|}{}\\
\multicolumn{2}{|p{14.5cm}|}{\VAR{selected_analysis['output'] | split_output_lines | filter_chars }} \\
\hline
\BLOCK{endif}
Entropy & \VAR{selected_analysis['entropy'] | nice_number} \\
\hline
\end{longtable}
}
\ No newline at end of file
Presenting: \VAR{selected_analysis}
render_test.tex
\ No newline at end of file
render_test.tex
\ No newline at end of file
Test \VAR{meta_data} - \VAR{analysis}
\ No newline at end of file
import logging
from base64 import decodebytes
from collections import OrderedDict
from contextlib import suppress
from pathlib import Path
from time import localtime, strftime
import jinja2
from common_helper_files import human_readable_file_size
GENERIC_TEMPLATE = 'generic.tex'
MAIN_TEMPLATE = 'main.tex'
META_TEMPLATE = 'meta.tex'
PLUGIN_TEMPLATE_BLUEPRINT = '{}.tex'
LOGO_FILE = 'fact_logo.png'
def render_number_as_size(number, verbose=True):
if not isinstance(number, (int, float)):
return 'not available'
if verbose:
return '{} ({})'.format(human_readable_file_size(int(number)), format(number, ',d') + ' Byte')
return human_readable_file_size(int(number))
def render_unix_time(unix_time_stamp):
if not isinstance(unix_time_stamp, (int, float)):
return 'not available'
return strftime('%Y-%m-%d %H:%M:%S', localtime(unix_time_stamp))
def render_number_as_string(number):
if isinstance(number, int):
return '{:,}'.format(number)
if isinstance(number, float):
return '{:,.2f}'.format(number)
if isinstance(number, str):
with suppress(ValueError):
return str(int(number))
return 'not available'
def replace_special_characters(data):
latex_character_escapes = OrderedDict()
latex_character_escapes['\\'] = ''
latex_character_escapes['\''] = ''
latex_character_escapes['$'] = '\\$'
latex_character_escapes['('] = '$($'
latex_character_escapes[')'] = '$)$'
latex_character_escapes['['] = '$[$'
latex_character_escapes[']'] = '$]$'
latex_character_escapes['#'] = '\\#'
latex_character_escapes['%'] = '\\%'
latex_character_escapes['&'] = '\\&'
latex_character_escapes['_'] = '\\_'
latex_character_escapes['{'] = '\\{'
latex_character_escapes['}'] = '\\}'
latex_character_escapes['^'] = '\\textasciicircum{}'
latex_character_escapes['~'] = '\\textasciitilde{}'
latex_character_escapes['>'] = '\\textgreater{}'
latex_character_escapes['<'] = '\\textless{}'
latex_character_escapes['\n'] = '\\newline '
for character, replacement in latex_character_escapes.items():
if character in data:
data = data.replace(character, replacement)
return data
def decode_base64_to_file(base64_string, filename, directory, suffix='png'):
file_path = Path(directory, '{}.{}'.format(filename, suffix))
file_path.write_bytes(decodebytes(base64_string.encode('utf-8')))
return str(file_path)
def replace_characters_in_list(list_of_strings):
return [
replace_special_characters(item) for item in list_of_strings
]
def split_hash_string(hash_string, max_length=61):
if len(hash_string) > max_length:
hash_string = '{}\n{}'.format(hash_string[:max_length], hash_string[max_length:])
return hash_string
def split_long_lines(multiline_string, max_length=92):
def evaluate_split(line):
return line if len(line) <= max_length else '{}\n{}'.format(line[:max_length], line[max_length:])
return ''.join(
evaluate_split(line) for line in multiline_string.splitlines(keepends=True)
)
def item_contains_string(item, string):
if not isinstance(item, str):
return False
return string in item
def create_jinja_environment(templates_to_use='default'):
template_directory = Path(Path(__file__).parent.parent, 'templates', templates_to_use)
environment = jinja2.Environment(
block_start_string=r'\BLOCK{',
block_end_string='}',
variable_start_string=r'\VAR{',
variable_end_string='}',
comment_start_string=r'\#{',
comment_end_string='}',
line_statement_prefix='%%',
line_comment_prefix='%#',
trim_blocks=True,
autoescape=False,
loader=jinja2.FileSystemLoader(str(template_directory))
)
_add_filters_to_jinja(environment)
return environment
def plugin_name(name):
return ' '.join((part.title() for part in name.split('_')))
def _add_filters_to_jinja(environment):
environment.filters['number_format'] = render_number_as_size
environment.filters['nice_unix_time'] = render_unix_time
environment.filters['nice_number'] = render_number_as_string
environment.filters['filter_chars'] = replace_special_characters
environment.filters['elements_count'] = len
environment.filters['base64_to_png'] = decode_base64_to_file
environment.filters['check_list'] = lambda x: x if x else ['list is empty']
environment.filters['plugin_name'] = plugin_name
environment.filters['filter_list'] = replace_characters_in_list
environment.filters['split_hash'] = split_hash_string
environment.filters['split_output_lines'] = split_long_lines
environment.filters['contains'] = item_contains_string
class TemplateEngine:
def __init__(self, template_folder=None, tmp_dir=None):
self._environment = create_jinja_environment(template_folder if template_folder else 'default')
self._tmp_dir = tmp_dir
def render_main_template(self, analysis, meta_data):
template = self._environment.get_template(MAIN_TEMPLATE)
return template.render(analysis=analysis, meta_data=meta_data)
def render_meta_template(self, meta_data):
template = self._environment.get_template(META_TEMPLATE)
return template.render(meta_data=meta_data)
def render_analysis_template(self, plugin, analysis):
try:
template = self._environment.get_template(PLUGIN_TEMPLATE_BLUEPRINT.format(plugin))
except jinja2.TemplateNotFound:
logging.warning('Falling back on generic template for {}'.format(plugin))
template = self._environment.get_template(GENERIC_TEMPLATE)
return template.render(plugin_name=plugin, selected_analysis=analysis, tmp_dir=self._tmp_dir)
[pytest]
addopts = -v
pep8ignore =
*.py E501
pytest
pytest-cov
pre-commit
requests
jinja2
git+https://github.com/fkie-cad/common_helper_files.git
git+https://github.com/fkie-cad/common_helper_process.git
import pytest
from pdf_generator.tex_generation.template_engine import TemplateEngine
from test.data.test_dict import TEST_DICT
# pylint: disable=redefined-outer-name
TEST_DATA = {
'analysis': {'file_hashes': {'ssdeep': 'bla', 'sha1': 'blah'}},
'meta_data': {'device_name': 'test_device'}
}
@pytest.fixture(scope='function')
def stub_engine():
return TemplateEngine()
def test_latex_code_generation(stub_engine: TemplateEngine):
result = stub_engine.render_meta_template(TEST_DICT)
assert result
def test_render_template(tmpdir):
engine = TemplateEngine(template_folder='test', tmp_dir=tmpdir)
test_data = {'meta_data': '123', 'analysis': '456'}
output = engine.render_analysis_template(plugin='render_test', analysis=test_data)
assert output == 'Test - '
import json
from pathlib import Path
import pytest
from pdf_generator.generator import (
LOGO_FILE, MAIN_TEMPLATE, META_TEMPLATE, PLUGIN_TEMPLATE_BLUEPRINT, copy_fact_image, create_report_filename,
create_templates, execute_latex, render_analysis_templates
)
class MockEngine:
def __init__(self, *_, **__):
pass
@staticmethod
def render_main_template(analysis, meta_data):
return '{}\n{}'.format(json.dumps(analysis), json.dumps(meta_data))
@staticmethod
def render_meta_template(meta_data):
return json.dumps(meta_data)
@staticmethod
def render_analysis_template(_, analysis):
return json.dumps(analysis)
def exec_mock(*_, **__):
Path('test').write_text('works')
def test_execute_latex(monkeypatch, tmpdir):
monkeypatch.setattr('pdf_generator.generator.execute_shell_command', exec_mock)
execute_latex(str(tmpdir))
assert Path(str(tmpdir), 'test').exists()
assert Path(str(tmpdir), 'test').read_text() == 'works'
def test_copy_fact_image(tmpdir):
copy_fact_image(str(tmpdir))
assert Path(str(tmpdir), LOGO_FILE).exists()
@pytest.mark.parametrize('device_name, pdf_name', [
('simple', 'simple_analysis_report.pdf'),
('harder name', 'harder_name_analysis_report.pdf'),
('dangerous/name', 'dangerous__name_analysis_report.pdf')
])
def test_create_report_filename(device_name, pdf_name):
assert create_report_filename({'device_name': device_name}) == pdf_name
def test_create_analysis_templates():
templates = render_analysis_templates(engine=MockEngine(), analysis={'test': {'result': 'data'}})
assert len(templates) == 1
filename, result_code = templates[0]
assert filename == PLUGIN_TEMPLATE_BLUEPRINT.format('test')
assert result_code == '{"result": "data"}'
def test_create_templates(monkeypatch, tmpdir):
monkeypatch.setattr('pdf_generator.generator.TemplateEngine', MockEngine)
create_templates(analysis={'test': {'result': 'data'}}, meta_data={}, tmp_dir=str(tmpdir))
assert Path(str(tmpdir), MAIN_TEMPLATE).exists()
assert Path(str(tmpdir), META_TEMPLATE).exists()
assert Path(str(tmpdir), PLUGIN_TEMPLATE_BLUEPRINT.format('test')).exists()
assert Path(str(tmpdir), PLUGIN_TEMPLATE_BLUEPRINT.format('test')).read_text() == '{"result": "data"}'
from pathlib import Path
import pytest
from pdf_generator.tex_generation.template_engine import (
TemplateEngine, decode_base64_to_file, render_number_as_size, render_number_as_string, render_unix_time,
replace_characters_in_list, replace_special_characters, split_hash_string, split_long_lines
)
# pylint: disable=redefined-outer-name
@pytest.fixture(scope='function')
def stub_engine(tmpdir):
return TemplateEngine(template_folder='test', tmp_dir=tmpdir)
def test_byte_number_filter():
assert render_number_as_size(None) == 'not available'
assert render_number_as_size(12, verbose=False) == '12.00 Byte'
assert render_number_as_size(128000) == '125.00 KiB (128,000 Byte)'
assert render_number_as_size(128000, verbose=False) == '125.00 KiB'
def test_nice_number_filter():
assert render_number_as_string(None) == 'not available'
assert render_number_as_string('no int') == 'not available'
assert render_number_as_string(12) == '12'
assert render_number_as_string(12.1) == '12.10'
assert render_number_as_string(12.101) == '12.10'
assert render_number_as_string(12.109) == '12.11'
assert render_number_as_string('12') == '12'
@pytest.mark.skip(reason='Since local time used, result is not stable')
def test_nice_unix_time():
assert render_unix_time(None) == 'not available'
assert render_unix_time(10) == '1970-01-01 01:00:10'
def test_split_hash():
assert split_hash_string('X' * 62) == '{}\nX'.format('X' * 61)
assert split_hash_string('X' * 61) == 'X' * 61
def test_split_output_lines():
assert split_long_lines('X\nX') == 'X\nX'
assert split_long_lines('{}\nX'.format('X' * 93)) == '{}\nX\nX'.format('X' * 92)
def test_convert_base64_to_png_filter(tmpdir):
decode_base64_to_file('0000', 'testfile', str(tmpdir))
assert Path(str(tmpdir), 'testfile.png').read_bytes() == b'\xd3\x4d\x34'
def test_filter_latex_special_chars():
assert replace_special_characters('safe') == 'safe'
assert replace_special_characters(r'C:\Windows') == r'C:Windows'
assert replace_special_characters(r'100 $') == r'100 \$'
def test_filter_chars_in_list():
assert replace_characters_in_list([]) == []
assert replace_characters_in_list([r'safe', r'un\safe']) == ['safe', 'unsafe']
def test_render_meta_template(stub_engine):
assert stub_engine.render_meta_template(meta_data='anything') == 'Test anything - '
def test_render_main_template(stub_engine):
assert stub_engine.render_main_template(meta_data='anything', analysis='else') == 'Test anything - else'
def test_render_analysis_template(stub_engine):
assert stub_engine.render_analysis_template(plugin='non_existing', analysis='result') == 'Presenting: result'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment