DMARCReporting 0.2.1__tar.gz → 0.3.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/__init__.py +1 -1
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/cli.py +9 -8
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/constants.py +2 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/decompressor.py +14 -2
- DMARCReporting-0.2.1/DMARCReporting/FileLister.py → dmarcreporting-0.3.1/DMARCReporting/filelister.py +1 -2
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/parser.py +17 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/PKG-INFO +13 -2
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/SOURCES.txt +2 -2
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/PKG-INFO +13 -2
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/setup.py +3 -1
- dmarcreporting-0.3.1/tests/test_acceptance.py +122 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_console_renderer.py +5 -3
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_csv_renderer.py +5 -3
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_decompressor.py +7 -1
- DMARCReporting-0.2.1/tests/test_FileLister.py → dmarcreporting-0.3.1/tests/test_filelister.py +12 -12
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_dkim.py +40 -2
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_dmarc.py +43 -6
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_parser_spf.py +32 -1
- DMARCReporting-0.2.1/tests/test_acceptance.py +0 -37
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/__main__.py +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/dns.py +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting/renderer.py +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/dependency_links.txt +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/entry_points.txt +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/requires.txt +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/DMARCReporting.egg-info/top_level.txt +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/LICENSE +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/README.md +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/setup.cfg +0 -0
- {DMARCReporting-0.2.1 → dmarcreporting-0.3.1}/tests/test_dns.py +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
from os.path import join
|
|
3
1
|
import io
|
|
4
2
|
|
|
5
3
|
from DMARCReporting.decompressor import DecompressorFactory
|
|
6
4
|
from DMARCReporting.parser import DMARCRuaParser
|
|
7
5
|
from DMARCReporting.renderer import ConsoleRenderer, CSVRenderer
|
|
8
6
|
from DMARCReporting.dns import DNS
|
|
9
|
-
from DMARCReporting.
|
|
7
|
+
from DMARCReporting.filelister import FileLister
|
|
10
8
|
|
|
11
9
|
|
|
12
10
|
class CLI():
|
|
@@ -14,14 +12,17 @@ class CLI():
|
|
|
14
12
|
files = FileLister().list(input_dir)
|
|
15
13
|
|
|
16
14
|
parser = DMARCRuaParser(DNS())
|
|
17
|
-
renderer = ConsoleRenderer()
|
|
18
15
|
all_data = []
|
|
19
16
|
|
|
20
17
|
for file in files:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
18
|
+
try:
|
|
19
|
+
report = DecompressorFactory.create(file).decompress(file)
|
|
20
|
+
data = parser.parse(io.BytesIO(report), include_all=show_all)
|
|
21
|
+
all_data += [[*row, file] for row in data]
|
|
22
|
+
except Exception as e:
|
|
23
|
+
print(f"Error processing file {file}: {e}")
|
|
24
|
+
|
|
25
|
+
renderer = ConsoleRenderer()
|
|
25
26
|
renderer.render("All", all_data)
|
|
26
27
|
|
|
27
28
|
if csv_output_file:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import gzip
|
|
2
2
|
import zipfile
|
|
3
|
+
import zlib
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class DecompressorFactory():
|
|
@@ -13,8 +14,19 @@ class DecompressorFactory():
|
|
|
13
14
|
|
|
14
15
|
class GZipDecompressor():
|
|
15
16
|
def decompress(self, gzip_file_path):
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
try:
|
|
18
|
+
with gzip.open(gzip_file_path, 'rb') as f:
|
|
19
|
+
return f.read()
|
|
20
|
+
except gzip.BadGzipFile:
|
|
21
|
+
return self._decompress_with_zlib(gzip_file_path)
|
|
22
|
+
|
|
23
|
+
def _decompress_with_zlib(self, gzip_file_path):
|
|
24
|
+
with open(gzip_file_path, 'rb') as f:
|
|
25
|
+
do = zlib.decompressobj(wbits=31)
|
|
26
|
+
data = do.decompress(f.read())
|
|
27
|
+
remainder = do.unused_data
|
|
28
|
+
f.seek(f.tell() - len(remainder))
|
|
29
|
+
return data
|
|
18
30
|
|
|
19
31
|
|
|
20
32
|
class ZipDecompressor():
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
from os import listdir
|
|
3
2
|
from os.path import isfile
|
|
4
3
|
from os.path import join
|
|
@@ -8,7 +7,7 @@ import pathlib
|
|
|
8
7
|
class FileLister():
|
|
9
8
|
def list(self, input_dir):
|
|
10
9
|
return sorted([
|
|
11
|
-
f for f in listdir(input_dir) if self._is_zip_or_gz_file(input_dir, f)
|
|
10
|
+
join(input_dir, f) for f in listdir(input_dir) if self._is_zip_or_gz_file(input_dir, f)
|
|
12
11
|
])
|
|
13
12
|
|
|
14
13
|
def _is_zip_or_gz_file(self, input_dir, file_name):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
|
|
1
3
|
from lxml import etree as ET
|
|
2
4
|
|
|
3
5
|
xpath_all = ".//record"
|
|
@@ -18,6 +20,9 @@ class DMARCRuaParser:
|
|
|
18
20
|
|
|
19
21
|
def parse(self, rua_report, include_all=False):
|
|
20
22
|
root = ET.parse(rua_report)
|
|
23
|
+
|
|
24
|
+
dates = self._parse_dates(root)
|
|
25
|
+
|
|
21
26
|
records = root.xpath(xpath_all if include_all else xpath_failed)
|
|
22
27
|
data = []
|
|
23
28
|
for record in records:
|
|
@@ -29,6 +34,8 @@ class DMARCRuaParser:
|
|
|
29
34
|
|
|
30
35
|
data.append(
|
|
31
36
|
[
|
|
37
|
+
dates["begin"],
|
|
38
|
+
dates["end"],
|
|
32
39
|
row["source_ip"],
|
|
33
40
|
row["source_host"],
|
|
34
41
|
payload_from,
|
|
@@ -43,6 +50,16 @@ class DMARCRuaParser:
|
|
|
43
50
|
)
|
|
44
51
|
return data
|
|
45
52
|
|
|
53
|
+
def _parse_dates(self, root):
|
|
54
|
+
result = {}
|
|
55
|
+
|
|
56
|
+
begin = int(root.xpath("/feedback/report_metadata/date_range/begin/text()")[0])
|
|
57
|
+
end = int(root.xpath("/feedback/report_metadata/date_range/end/text()")[0])
|
|
58
|
+
|
|
59
|
+
result["begin"] = datetime.fromtimestamp(begin).isoformat()
|
|
60
|
+
result["end"] = datetime.fromtimestamp(end).isoformat()
|
|
61
|
+
return result
|
|
62
|
+
|
|
46
63
|
def _parse_row(self, record):
|
|
47
64
|
result = {}
|
|
48
65
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: DMARCReporting
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Simple tool to extract error reports from DMARC files
|
|
5
5
|
Home-page: https://github.com/MozaicWorks/DMARCReporting
|
|
6
6
|
Author: Mozaic Works
|
|
@@ -13,6 +13,17 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: lxml
|
|
15
15
|
Requires-Dist: tabulate
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: license
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
16
27
|
|
|
17
28
|
# DMARCReporting 
|
|
18
29
|
|
|
@@ -2,13 +2,13 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.cfg
|
|
4
4
|
setup.py
|
|
5
|
-
DMARCReporting/FileLister.py
|
|
6
5
|
DMARCReporting/__init__.py
|
|
7
6
|
DMARCReporting/__main__.py
|
|
8
7
|
DMARCReporting/cli.py
|
|
9
8
|
DMARCReporting/constants.py
|
|
10
9
|
DMARCReporting/decompressor.py
|
|
11
10
|
DMARCReporting/dns.py
|
|
11
|
+
DMARCReporting/filelister.py
|
|
12
12
|
DMARCReporting/parser.py
|
|
13
13
|
DMARCReporting/renderer.py
|
|
14
14
|
DMARCReporting.egg-info/PKG-INFO
|
|
@@ -17,12 +17,12 @@ DMARCReporting.egg-info/dependency_links.txt
|
|
|
17
17
|
DMARCReporting.egg-info/entry_points.txt
|
|
18
18
|
DMARCReporting.egg-info/requires.txt
|
|
19
19
|
DMARCReporting.egg-info/top_level.txt
|
|
20
|
-
tests/test_FileLister.py
|
|
21
20
|
tests/test_acceptance.py
|
|
22
21
|
tests/test_console_renderer.py
|
|
23
22
|
tests/test_csv_renderer.py
|
|
24
23
|
tests/test_decompressor.py
|
|
25
24
|
tests/test_dns.py
|
|
25
|
+
tests/test_filelister.py
|
|
26
26
|
tests/test_parser_dkim.py
|
|
27
27
|
tests/test_parser_dmarc.py
|
|
28
28
|
tests/test_parser_spf.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: DMARCReporting
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Simple tool to extract error reports from DMARC files
|
|
5
5
|
Home-page: https://github.com/MozaicWorks/DMARCReporting
|
|
6
6
|
Author: Mozaic Works
|
|
@@ -13,6 +13,17 @@ Description-Content-Type: text/markdown
|
|
|
13
13
|
License-File: LICENSE
|
|
14
14
|
Requires-Dist: lxml
|
|
15
15
|
Requires-Dist: tabulate
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: author-email
|
|
18
|
+
Dynamic: classifier
|
|
19
|
+
Dynamic: description
|
|
20
|
+
Dynamic: description-content-type
|
|
21
|
+
Dynamic: home-page
|
|
22
|
+
Dynamic: license
|
|
23
|
+
Dynamic: license-file
|
|
24
|
+
Dynamic: requires-dist
|
|
25
|
+
Dynamic: requires-python
|
|
26
|
+
Dynamic: summary
|
|
16
27
|
|
|
17
28
|
# DMARCReporting 
|
|
18
29
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import pathlib
|
|
2
2
|
from setuptools import setup, find_packages
|
|
3
3
|
|
|
4
|
+
import DMARCReporting
|
|
5
|
+
|
|
4
6
|
# The directory containing this file
|
|
5
7
|
HERE = pathlib.Path(__file__).parent
|
|
6
8
|
|
|
@@ -10,7 +12,7 @@ README = (HERE / "README.md").read_text()
|
|
|
10
12
|
# This call to setup() does all the work
|
|
11
13
|
setup(
|
|
12
14
|
name="DMARCReporting",
|
|
13
|
-
version=
|
|
15
|
+
version=DMARCReporting.__version__,
|
|
14
16
|
description="Simple tool to extract error reports from DMARC files",
|
|
15
17
|
long_description=README,
|
|
16
18
|
long_description_content_type="text/markdown",
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
import io
|
|
4
|
+
import time
|
|
5
|
+
from unittest.mock import patch
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from .context import DMARCReporting # noqa F401
|
|
10
|
+
from DMARCReporting.cli import CLI
|
|
11
|
+
|
|
12
|
+
test_tz = 'Europe/Brussels'
|
|
13
|
+
env_tz = ''
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture
|
|
17
|
+
def setup_timezone():
|
|
18
|
+
env_tz = time.tzname[0]
|
|
19
|
+
print()
|
|
20
|
+
print(f"save current timezone: {env_tz}")
|
|
21
|
+
print(f"set timezone for tests to {test_tz}")
|
|
22
|
+
os.environ['TZ'] = test_tz
|
|
23
|
+
time.tzset()
|
|
24
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
25
|
+
yield
|
|
26
|
+
print()
|
|
27
|
+
print(f"reset timezone back to {env_tz}")
|
|
28
|
+
os.environ['TZ'] = env_tz
|
|
29
|
+
time.tzset()
|
|
30
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@patch('socket.gethostbyaddr')
|
|
34
|
+
def test_render(gethostbyaddr_mock, setup_timezone):
|
|
35
|
+
gethostbyaddr_mock.side_effect = [
|
|
36
|
+
("Unknown host", [], []),
|
|
37
|
+
("208-90-221-45.static.flhsi.com", [], []),
|
|
38
|
+
("Unknown host", [], []),
|
|
39
|
+
("208-90-221-45.static.flhsi.com", [], []),
|
|
40
|
+
]
|
|
41
|
+
try:
|
|
42
|
+
output = io.StringIO()
|
|
43
|
+
sys.stdout = output
|
|
44
|
+
|
|
45
|
+
cli = CLI()
|
|
46
|
+
cli.execute("./samples")
|
|
47
|
+
|
|
48
|
+
expected = (
|
|
49
|
+
"Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
|
|
50
|
+
"------------------- ------------------- ------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- ------------------------\n" # noqa E501
|
|
51
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 ./samples/report.xml.gz\n" # noqa E501
|
|
52
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 ./samples/report.xml.gz\n" # noqa E501
|
|
53
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 ./samples/report.xml.zip\n" # noqa E501
|
|
54
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 ./samples/report.xml.zip\n" # noqa E501
|
|
55
|
+
)
|
|
56
|
+
actual = output.getvalue()
|
|
57
|
+
|
|
58
|
+
assert expected == actual
|
|
59
|
+
|
|
60
|
+
finally:
|
|
61
|
+
sys.stdout = sys.__stdout__
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@patch('socket.gethostbyaddr')
|
|
65
|
+
def test_render_all(gethostbyaddr_mock, setup_timezone):
|
|
66
|
+
gethostbyaddr_mock.side_effect = [
|
|
67
|
+
("smtp.bellous.com", [], []),
|
|
68
|
+
("smtp.bellous.com", [], []),
|
|
69
|
+
("smtp.bellous.com", [], []),
|
|
70
|
+
("smtp.bellous.com", [], []),
|
|
71
|
+
("smtp.bellous.com", [], []),
|
|
72
|
+
("Unknown host", [], []),
|
|
73
|
+
("208-90-221-45.static.flhsi.com", [], []),
|
|
74
|
+
("smtp.disicious.com", [], []),
|
|
75
|
+
("smtp.disicious.com", [], []),
|
|
76
|
+
("smtp.disicious.com", [], []),
|
|
77
|
+
("smtp.disicious.com", [], []),
|
|
78
|
+
("smtp.disicious.com", [], []),
|
|
79
|
+
("Unknown host", [], []),
|
|
80
|
+
("208-90-221-45.static.flhsi.com", [], []),
|
|
81
|
+
("smtp.abbove.com", [], []),
|
|
82
|
+
("smtp.abbove.com", [], []),
|
|
83
|
+
("smtp.abbove.com", [], []),
|
|
84
|
+
("smtp.abbove.com", [], []),
|
|
85
|
+
("smtp.abbove.com", [], []),
|
|
86
|
+
]
|
|
87
|
+
try:
|
|
88
|
+
output = io.StringIO()
|
|
89
|
+
sys.stdout = output
|
|
90
|
+
|
|
91
|
+
cli = CLI()
|
|
92
|
+
cli.execute("./samples", csv_output_file=None, show_all=True)
|
|
93
|
+
|
|
94
|
+
expected = (
|
|
95
|
+
"Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
|
|
96
|
+
"------------------- ------------------- -------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- ----------------------------------\n" # noqa E501
|
|
97
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 207.90.221.45 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 8 ./samples/report.xml.gz\n" # noqa E501
|
|
98
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 207.86.220.40 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 22 ./samples/report.xml.gz\n" # noqa E501
|
|
99
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 198.3.186.1 smtp.bellous.com bellous.com mail.bellous.com none pass pass pass pass 68 ./samples/report.xml.gz\n" # noqa E501
|
|
100
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 210.81.222.70 smtp.bellous.com bellous.com bellous.com none pass pass pass pass 6 ./samples/report.xml.gz\n" # noqa E501
|
|
101
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 197.3.177.11 smtp.bellous.com bellous.com mail.bellous.com none pass pass pass pass 81 ./samples/report.xml.gz\n" # noqa E501
|
|
102
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 ./samples/report.xml.gz\n" # noqa E501
|
|
103
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 ./samples/report.xml.gz\n" # noqa E501
|
|
104
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 207.90.221.45 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 8 ./samples/report.xml.zip\n" # noqa E501
|
|
105
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 207.86.220.40 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 22 ./samples/report.xml.zip\n" # noqa E501
|
|
106
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 198.3.186.1 smtp.disicious.com disicious.com mail.disicious.com none pass pass pass pass 68 ./samples/report.xml.zip\n" # noqa E501
|
|
107
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 210.81.222.70 smtp.disicious.com disicious.com disicious.com none pass pass pass pass 6 ./samples/report.xml.zip\n" # noqa E501
|
|
108
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 197.3.177.11 smtp.disicious.com disicious.com mail.disicious.com none pass pass pass pass 81 ./samples/report.xml.zip\n" # noqa E501
|
|
109
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 ./samples/report.xml.zip\n" # noqa E501
|
|
110
|
+
"2021-11-25T01:00:00 2021-11-26T00:59:59 208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 ./samples/report.xml.zip\n" # noqa E501
|
|
111
|
+
"2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.179 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
|
|
112
|
+
"2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.170 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
|
|
113
|
+
"2023-08-30T02:00:00 2023-08-31T01:59:59 209.85.208.181 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage1.xml.gz\n" # noqa E501
|
|
114
|
+
"2023-08-31T02:00:00 2023-09-01T01:59:59 209.85.219.176 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage2.xml.gz\n" # noqa E501
|
|
115
|
+
"2023-08-31T02:00:00 2023-09-01T01:59:59 209.85.219.178 smtp.abbove.com abbove.com abbove.com none pass pass pass pass 1 ./samples/trailing_garbage2.xml.gz\n" # noqa E501
|
|
116
|
+
)
|
|
117
|
+
actual = output.getvalue()
|
|
118
|
+
|
|
119
|
+
assert expected == actual
|
|
120
|
+
|
|
121
|
+
finally:
|
|
122
|
+
sys.stdout = sys.__stdout__
|
|
@@ -31,6 +31,8 @@ def test_render_with_data():
|
|
|
31
31
|
|
|
32
32
|
data = [
|
|
33
33
|
[
|
|
34
|
+
"begin_date",
|
|
35
|
+
"end_date",
|
|
34
36
|
"source_ip",
|
|
35
37
|
"source_host",
|
|
36
38
|
"payload_from",
|
|
@@ -49,9 +51,9 @@ def test_render_with_data():
|
|
|
49
51
|
sut.render("ruareport_file", data)
|
|
50
52
|
|
|
51
53
|
expected = (
|
|
52
|
-
"Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
|
|
53
|
-
"----------- ------------- ---------------------- -------------------------- ----------------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
|
|
54
|
-
"source_ip source_host payload_from envelop_from dmarc_disposition dkim_aligned dkim_auth spf_aligned spf_auth 42 ruareport_file\n" # noqa E501
|
|
54
|
+
"Begin Date End Date Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
|
|
55
|
+
"------------ ---------- ----------- ------------- ---------------------- -------------------------- ----------------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
|
|
56
|
+
"begin_date end_date source_ip source_host payload_from envelop_from dmarc_disposition dkim_aligned dkim_auth spf_aligned spf_auth 42 ruareport_file\n" # noqa E501
|
|
55
57
|
)
|
|
56
58
|
actual = output.getvalue()
|
|
57
59
|
|
|
@@ -15,7 +15,7 @@ def test_render_without_data():
|
|
|
15
15
|
sut = CSVRenderer()
|
|
16
16
|
sut.render(csv_file, data)
|
|
17
17
|
|
|
18
|
-
expected = "Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
|
|
18
|
+
expected = "Begin Date,End Date,Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
|
|
19
19
|
actual = open(csv_file, "r").read()
|
|
20
20
|
|
|
21
21
|
assert expected == actual
|
|
@@ -31,6 +31,8 @@ def test_render_with_data():
|
|
|
31
31
|
|
|
32
32
|
data = [
|
|
33
33
|
[
|
|
34
|
+
"begin_date",
|
|
35
|
+
"end_date",
|
|
34
36
|
"source_ip",
|
|
35
37
|
"source_host",
|
|
36
38
|
"payload_from",
|
|
@@ -49,8 +51,8 @@ def test_render_with_data():
|
|
|
49
51
|
sut.render(csv_file, data)
|
|
50
52
|
|
|
51
53
|
expected = (
|
|
52
|
-
"Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
|
|
53
|
-
"source_ip,source_host,payload_from,envelop_from,dmarc_disposition,dkim_aligned,dkim_auth,spf_aligned,spf_auth,42,ruareport_file\n" # noqa E501
|
|
54
|
+
"Begin Date,End Date,Source IP,Source Host,Payload From (From:),Envelop From (MAIL FROM),DMARC,DKIM Align,DKIM Auth,SPF Align,SPF Auth,Count,File\n" # noqa E501
|
|
55
|
+
"begin_date,end_date,source_ip,source_host,payload_from,envelop_from,dmarc_disposition,dkim_aligned,dkim_auth,spf_aligned,spf_auth,42,ruareport_file\n" # noqa E501
|
|
54
56
|
)
|
|
55
57
|
actual = actual = open(csv_file, "r").read()
|
|
56
58
|
|
|
@@ -30,7 +30,6 @@ def test_factory_raises_not_implemented():
|
|
|
30
30
|
def test_decompress_gzip():
|
|
31
31
|
sut = GZipDecompressor()
|
|
32
32
|
actual = sut.decompress("./tests/data/sample.txt.gz")
|
|
33
|
-
|
|
34
33
|
assert "Hello world, gzip!" == actual.decode("utf-8")
|
|
35
34
|
|
|
36
35
|
|
|
@@ -39,3 +38,10 @@ def test_decompress_zip():
|
|
|
39
38
|
actual = sut.decompress("./tests/data/sample.txt.zip")
|
|
40
39
|
|
|
41
40
|
assert "Hello world, zip!" == actual.decode("utf-8")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_decompress_trailing_garbage_gzip():
|
|
44
|
+
sut = GZipDecompressor()
|
|
45
|
+
actual = sut.decompress("./tests/data/trailing_garbage.xml.gz")
|
|
46
|
+
expected = open("./tests/data/trailing_garbage.xml", "r").read()
|
|
47
|
+
assert expected == actual.decode("utf-8")
|
DMARCReporting-0.2.1/tests/test_FileLister.py → dmarcreporting-0.3.1/tests/test_filelister.py
RENAMED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
2
1
|
import unittest
|
|
3
|
-
from .context import DMARCReporting # noqa F401
|
|
4
|
-
from DMARCReporting.FileLister import FileLister
|
|
5
2
|
import tempfile
|
|
6
3
|
import os
|
|
4
|
+
from os.path import join
|
|
5
|
+
|
|
6
|
+
from .context import DMARCReporting # noqa F401
|
|
7
|
+
from DMARCReporting.filelister import FileLister
|
|
8
|
+
|
|
7
9
|
from parameterized import parameterized
|
|
8
10
|
|
|
9
11
|
|
|
@@ -17,19 +19,17 @@ class TestFileLister(unittest.TestCase):
|
|
|
17
19
|
("sort_files", ["2.gz", "z.zip", "1.gz", "a.zip"], ["1.gz", "2.gz", "a.zip", "z.zip"]),
|
|
18
20
|
("only_gz_and_zip_files", ["z.zip", "1.gz", "3.txt", "ttt.jpg"], ["1.gz", "z.zip"]),
|
|
19
21
|
])
|
|
20
|
-
def test_file_lister(self, name, filesList,
|
|
22
|
+
def test_file_lister(self, name, filesList, expectedFilesList):
|
|
21
23
|
def listerFunction(dirName): return FileLister().list(dirName)
|
|
22
24
|
|
|
23
|
-
|
|
25
|
+
# Create temporary directory, temporary files, list them with the lister function, assert, and delete them
|
|
26
|
+
with tempfile.TemporaryDirectory() as testDir:
|
|
27
|
+
[self.create_test_file(testDir, fileName) for fileName in filesList]
|
|
28
|
+
actual = listerFunction(testDir)
|
|
29
|
+
expected = [join(testDir, f) for f in expectedFilesList]
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
self.assertListEqual(expected, actual)
|
|
26
32
|
|
|
27
33
|
def create_test_file(self, path, filename):
|
|
28
34
|
with open(os.path.join(path, filename), 'w'):
|
|
29
35
|
pass
|
|
30
|
-
|
|
31
|
-
def list_files_with_function(self, filesList, listerFunction):
|
|
32
|
-
''' Create temporary directory, temporary files, list them with the lister function, and delete them'''
|
|
33
|
-
with tempfile.TemporaryDirectory() as testDir:
|
|
34
|
-
[self.create_test_file(testDir, fileName) for fileName in filesList]
|
|
35
|
-
return listerFunction(testDir)
|
|
@@ -1,16 +1,44 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import io
|
|
3
|
+
import time
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
5
7
|
from .context import DMARCReporting # noqa F401
|
|
6
8
|
from DMARCReporting.parser import DMARCRuaParser
|
|
7
9
|
|
|
10
|
+
test_tz = 'Europe/Brussels'
|
|
11
|
+
env_tz = ''
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture
|
|
15
|
+
def setup_timezone():
|
|
16
|
+
env_tz = time.tzname[0]
|
|
17
|
+
print()
|
|
18
|
+
print(f"save current timezone: {env_tz}")
|
|
19
|
+
print(f"set timezone for tests to {test_tz}")
|
|
20
|
+
os.environ['TZ'] = test_tz
|
|
21
|
+
time.tzset()
|
|
22
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
23
|
+
yield
|
|
24
|
+
print()
|
|
25
|
+
print(f"reset timezone back to {env_tz}")
|
|
26
|
+
os.environ['TZ'] = env_tz
|
|
27
|
+
time.tzset()
|
|
28
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
29
|
+
|
|
8
30
|
|
|
9
31
|
@pytest.fixture
|
|
10
32
|
def not_authenticated():
|
|
11
33
|
return io.StringIO(
|
|
12
34
|
"""
|
|
13
35
|
<feedback>
|
|
36
|
+
<report_metadata>
|
|
37
|
+
<date_range>
|
|
38
|
+
<begin>1703237232</begin>
|
|
39
|
+
<end>1703280196</end>
|
|
40
|
+
</date_range>
|
|
41
|
+
</report_metadata>
|
|
14
42
|
<record>
|
|
15
43
|
<row>
|
|
16
44
|
<source_ip>201.81.220.40</source_ip>
|
|
@@ -44,11 +72,13 @@ class DNSStub:
|
|
|
44
72
|
return "mail.email.com"
|
|
45
73
|
|
|
46
74
|
|
|
47
|
-
def test_when_not_authenticated(not_authenticated):
|
|
75
|
+
def test_when_not_authenticated(not_authenticated, setup_timezone):
|
|
48
76
|
sut = DMARCRuaParser(DNSStub())
|
|
49
77
|
actual = sut.parse(not_authenticated)
|
|
50
78
|
expected = [
|
|
51
79
|
[
|
|
80
|
+
"2023-12-22T10:27:12",
|
|
81
|
+
"2023-12-22T22:23:16",
|
|
52
82
|
"201.81.220.40",
|
|
53
83
|
"mail.email.com",
|
|
54
84
|
"email.com",
|
|
@@ -69,6 +99,12 @@ def authenticated_two_records():
|
|
|
69
99
|
return io.StringIO(
|
|
70
100
|
"""
|
|
71
101
|
<feedback>
|
|
102
|
+
<report_metadata>
|
|
103
|
+
<date_range>
|
|
104
|
+
<begin>1703237232</begin>
|
|
105
|
+
<end>1703280196</end>
|
|
106
|
+
</date_range>
|
|
107
|
+
</report_metadata>
|
|
72
108
|
<record>
|
|
73
109
|
<row>
|
|
74
110
|
<source_ip>201.81.220.40</source_ip>
|
|
@@ -102,11 +138,13 @@ def authenticated_two_records():
|
|
|
102
138
|
)
|
|
103
139
|
|
|
104
140
|
|
|
105
|
-
def test_when_not_authenticated_having_two_records(authenticated_two_records):
|
|
141
|
+
def test_when_not_authenticated_having_two_records(authenticated_two_records, setup_timezone):
|
|
106
142
|
sut = DMARCRuaParser(DNSStub())
|
|
107
143
|
actual = sut.parse(authenticated_two_records)
|
|
108
144
|
expected = [
|
|
109
145
|
[
|
|
146
|
+
"2023-12-22T10:27:12",
|
|
147
|
+
"2023-12-22T22:23:16",
|
|
110
148
|
"201.81.220.40",
|
|
111
149
|
"mail.email.com",
|
|
112
150
|
"email.com",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import io
|
|
3
|
+
import time
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
@@ -6,10 +8,37 @@ from .context import DMARCReporting # noqa F401
|
|
|
6
8
|
from DMARCReporting.parser import DMARCRuaParser
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
test_tz = 'Europe/Brussels'
|
|
12
|
+
env_tz = ''
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def setup_timezone():
|
|
17
|
+
env_tz = time.tzname[0]
|
|
18
|
+
print()
|
|
19
|
+
print(f"save current timezone: {env_tz}")
|
|
20
|
+
print(f"set timezone for tests to {test_tz}")
|
|
21
|
+
os.environ['TZ'] = test_tz
|
|
22
|
+
time.tzset()
|
|
23
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
24
|
+
yield
|
|
25
|
+
print()
|
|
26
|
+
print(f"reset timezone back to {env_tz}")
|
|
27
|
+
os.environ['TZ'] = env_tz
|
|
28
|
+
time.tzset()
|
|
29
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
30
|
+
|
|
31
|
+
|
|
9
32
|
def rua_report(disposition="none", spf_aligned="pass", dkim_aligned="pass"):
|
|
10
33
|
return io.StringIO(
|
|
11
34
|
"""
|
|
12
35
|
<feedback>
|
|
36
|
+
<report_metadata>
|
|
37
|
+
<date_range>
|
|
38
|
+
<begin>1703237232</begin>
|
|
39
|
+
<end>1703280196</end>
|
|
40
|
+
</date_range>
|
|
41
|
+
</report_metadata>
|
|
13
42
|
<record>
|
|
14
43
|
<row>
|
|
15
44
|
<source_ip>101.0.122.38</source_ip>
|
|
@@ -79,11 +108,13 @@ class DNSStub:
|
|
|
79
108
|
return "mail.email.com"
|
|
80
109
|
|
|
81
110
|
|
|
82
|
-
def test_when_dmarc_disposition_quarantine(rua_report_quarantine):
|
|
111
|
+
def test_when_dmarc_disposition_quarantine(rua_report_quarantine, setup_timezone):
|
|
83
112
|
sut = DMARCRuaParser(DNSStub())
|
|
84
113
|
actual = sut.parse(rua_report_quarantine)
|
|
85
114
|
expected = [
|
|
86
115
|
[
|
|
116
|
+
"2023-12-22T10:27:12",
|
|
117
|
+
"2023-12-22T22:23:16",
|
|
87
118
|
"101.0.122.38",
|
|
88
119
|
"mail.email.com",
|
|
89
120
|
"email.com",
|
|
@@ -99,17 +130,19 @@ def test_when_dmarc_disposition_quarantine(rua_report_quarantine):
|
|
|
99
130
|
assert expected == actual
|
|
100
131
|
|
|
101
132
|
|
|
102
|
-
def test_when_dmarc_disposition_none(rua_report_none):
|
|
133
|
+
def test_when_dmarc_disposition_none(rua_report_none, setup_timezone):
|
|
103
134
|
sut = DMARCRuaParser(DNSStub())
|
|
104
135
|
actual = sut.parse(rua_report_none)
|
|
105
136
|
assert [] == actual
|
|
106
137
|
|
|
107
138
|
|
|
108
|
-
def test_when_dmarc_disposition_reject(rua_report_reject):
|
|
139
|
+
def test_when_dmarc_disposition_reject(rua_report_reject, setup_timezone):
|
|
109
140
|
sut = DMARCRuaParser(DNSStub())
|
|
110
141
|
actual = sut.parse(rua_report_reject)
|
|
111
142
|
expected = [
|
|
112
143
|
[
|
|
144
|
+
"2023-12-22T10:27:12",
|
|
145
|
+
"2023-12-22T22:23:16",
|
|
113
146
|
"101.0.122.38",
|
|
114
147
|
"mail.email.com",
|
|
115
148
|
"email.com",
|
|
@@ -125,17 +158,19 @@ def test_when_dmarc_disposition_reject(rua_report_reject):
|
|
|
125
158
|
assert expected == actual
|
|
126
159
|
|
|
127
160
|
|
|
128
|
-
def test_when_spf_and_dkim_aligned(rua_report_spf_and_dkim_aligned):
|
|
161
|
+
def test_when_spf_and_dkim_aligned(rua_report_spf_and_dkim_aligned, setup_timezone):
|
|
129
162
|
sut = DMARCRuaParser(DNSStub())
|
|
130
163
|
actual = sut.parse(rua_report_spf_and_dkim_aligned)
|
|
131
164
|
assert [] == actual
|
|
132
165
|
|
|
133
166
|
|
|
134
|
-
def test_when_spf_not_aligned(rua_report_spf_not_aligned):
|
|
167
|
+
def test_when_spf_not_aligned(rua_report_spf_not_aligned, setup_timezone):
|
|
135
168
|
sut = DMARCRuaParser(DNSStub())
|
|
136
169
|
actual = sut.parse(rua_report_spf_not_aligned)
|
|
137
170
|
expected = [
|
|
138
171
|
[
|
|
172
|
+
"2023-12-22T10:27:12",
|
|
173
|
+
"2023-12-22T22:23:16",
|
|
139
174
|
"101.0.122.38",
|
|
140
175
|
"mail.email.com",
|
|
141
176
|
"email.com",
|
|
@@ -151,11 +186,13 @@ def test_when_spf_not_aligned(rua_report_spf_not_aligned):
|
|
|
151
186
|
assert expected == actual
|
|
152
187
|
|
|
153
188
|
|
|
154
|
-
def test_when_dkim_not_aligned(rua_report_dkim_not_aligned):
|
|
189
|
+
def test_when_dkim_not_aligned(rua_report_dkim_not_aligned, setup_timezone):
|
|
155
190
|
sut = DMARCRuaParser(DNSStub())
|
|
156
191
|
actual = sut.parse(rua_report_dkim_not_aligned)
|
|
157
192
|
expected = [
|
|
158
193
|
[
|
|
194
|
+
"2023-12-22T10:27:12",
|
|
195
|
+
"2023-12-22T22:23:16",
|
|
159
196
|
"101.0.122.38",
|
|
160
197
|
"mail.email.com",
|
|
161
198
|
"email.com",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import os
|
|
1
2
|
import io
|
|
3
|
+
import time
|
|
2
4
|
|
|
3
5
|
import pytest
|
|
4
6
|
|
|
@@ -6,11 +8,38 @@ from .context import DMARCReporting # noqa F401
|
|
|
6
8
|
from DMARCReporting.parser import DMARCRuaParser
|
|
7
9
|
|
|
8
10
|
|
|
11
|
+
test_tz = 'Europe/Brussels'
|
|
12
|
+
env_tz = ''
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture
|
|
16
|
+
def setup_timezone():
|
|
17
|
+
env_tz = time.tzname[0]
|
|
18
|
+
print()
|
|
19
|
+
print(f"save current timezone: {env_tz}")
|
|
20
|
+
print(f"set timezone for tests to {test_tz}")
|
|
21
|
+
os.environ['TZ'] = test_tz
|
|
22
|
+
time.tzset()
|
|
23
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
24
|
+
yield
|
|
25
|
+
print()
|
|
26
|
+
print(f"reset timezone back to {env_tz}")
|
|
27
|
+
os.environ['TZ'] = env_tz
|
|
28
|
+
time.tzset()
|
|
29
|
+
print(f"timezone is now {time.tzname[0]}")
|
|
30
|
+
|
|
31
|
+
|
|
9
32
|
@pytest.fixture
|
|
10
33
|
def not_authenticated():
|
|
11
34
|
return io.StringIO(
|
|
12
35
|
"""
|
|
13
36
|
<feedback>
|
|
37
|
+
<report_metadata>
|
|
38
|
+
<date_range>
|
|
39
|
+
<begin>1703237232</begin>
|
|
40
|
+
<end>1703280196</end>
|
|
41
|
+
</date_range>
|
|
42
|
+
</report_metadata>
|
|
14
43
|
<record>
|
|
15
44
|
<row>
|
|
16
45
|
<source_ip>201.81.220.40</source_ip>
|
|
@@ -43,11 +72,13 @@ class DNSStub:
|
|
|
43
72
|
return "mail.email.com"
|
|
44
73
|
|
|
45
74
|
|
|
46
|
-
def test_when_spf_not_authenticated(not_authenticated):
|
|
75
|
+
def test_when_spf_not_authenticated(not_authenticated, setup_timezone):
|
|
47
76
|
sut = DMARCRuaParser(DNSStub())
|
|
48
77
|
actual = sut.parse(not_authenticated)
|
|
49
78
|
expected = [
|
|
50
79
|
[
|
|
80
|
+
"2023-12-22T10:27:12",
|
|
81
|
+
"2023-12-22T22:23:16",
|
|
51
82
|
"201.81.220.40",
|
|
52
83
|
"mail.email.com",
|
|
53
84
|
"email.com",
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import io
|
|
2
|
-
import sys
|
|
3
|
-
from unittest.mock import patch
|
|
4
|
-
|
|
5
|
-
from .context import DMARCReporting # noqa F401
|
|
6
|
-
from DMARCReporting.cli import CLI
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@patch('socket.gethostbyaddr')
|
|
10
|
-
def test_render(gethostbyaddr_mock):
|
|
11
|
-
gethostbyaddr_mock.side_effect = [
|
|
12
|
-
("Unknown host", [], []),
|
|
13
|
-
("208-90-221-45.static.flhsi.com", [], []),
|
|
14
|
-
("Unknown host", [], []),
|
|
15
|
-
("208-90-221-45.static.flhsi.com", [], []),
|
|
16
|
-
]
|
|
17
|
-
try:
|
|
18
|
-
output = io.StringIO()
|
|
19
|
-
sys.stdout = output
|
|
20
|
-
|
|
21
|
-
cli = CLI()
|
|
22
|
-
cli.execute("./samples")
|
|
23
|
-
|
|
24
|
-
expected = (
|
|
25
|
-
"Source IP Source Host Payload From (From:) Envelop From (MAIL FROM) DMARC DKIM Align DKIM Auth SPF Align SPF Auth Count File\n" # noqa E501
|
|
26
|
-
"------------- ------------------------------ ---------------------- -------------------------- ------- ------------ ----------- ----------- ---------- ------- --------------\n" # noqa E501
|
|
27
|
-
"80.96.161.193 Unknown host bellous.com bellous.com none pass pass fail fail 3 report.xml.gz\n" # noqa E501
|
|
28
|
-
"208.90.221.45 208-90-221-45.static.flhsi.com bellous.com calendar.yambo.com none pass pass fail pass 32 report.xml.gz\n" # noqa E501
|
|
29
|
-
"80.96.161.193 Unknown host disicious.com disicious.com none pass pass fail fail 3 report.xml.zip\n" # noqa E501
|
|
30
|
-
"208.90.221.45 208-90-221-45.static.flhsi.com disicious.com calendar.trumbee.com none pass pass fail pass 32 report.xml.zip\n" # noqa E501
|
|
31
|
-
)
|
|
32
|
-
actual = output.getvalue()
|
|
33
|
-
|
|
34
|
-
assert expected == actual
|
|
35
|
-
|
|
36
|
-
finally:
|
|
37
|
-
sys.stdout = sys.__stdout__
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|