import os import shutil import sqlite3 import sys import tempfile import unittest from unittest import mock sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'payload', 'user', 'remote_access', 'pager-webui')) import server def setUpModule(): __import__('importlib').reload(server) SCHEMA = ''' CREATE TABLE scan(id INTEGER PRIMARY KEY AUTOINCREMENT, uuid TEXT, time INT, name TEXT); CREATE TABLE wifi_device(hash INT PRIMARY KEY, scan INT, mac TEXT, time INT, signal INT, freq INT, packets INT); CREATE TABLE ssid(hash INT PRIMARY KEY, wifi_device INT, scan INT, type INT, bssid TEXT, ssid BLOB, hidden INT, time INT, signal INT, freq INT, channel INT, encryption INT); CREATE TABLE handshake(hash INT PRIMARY KEY, scan INT, stahash INT, aphash INT, time INT, beacon BLOB, hs1 BLOB, hs2 BLOB, hs3 BLOB, hs4 BLOB); CREATE TABLE hostap_basic(id INT PRIMARY KEY, scan INT, time INT, type TEXT, identity TEXT, password TEXT, verified INT NOT NULL DEFAULT 0); CREATE TABLE hostap_chalresp(id INT PRIMARY KEY, scan INT, time INT, type TEXT, username TEXT, challenge BLOB, response BLOB, verified INT NOT NULL DEFAULT 0); CREATE TABLE hostap_client(id INT PRIMARY KEY, scan INT, hash INT, mac TEXT, ssid BLOB, connected_time INT, disconnected_time INT); CREATE TABLE hostap_handshake(id INT PRIMARY KEY, scan INT, time INT, type INT, mic BLOB, apmac BLOB, stamac BLOB, ssid BLOB, nonce BLOB, eapol BLOB); ''' def make_db(): fd, db = tempfile.mkstemp(suffix='.db') os.close(fd) conn = sqlite3.connect(db) conn.executescript(SCHEMA) conn.execute("INSERT INTO scan (uuid, time, name) VALUES ('u1', 1786466531, 'pager')") conn.execute("INSERT INTO scan (uuid, time, name) VALUES ('u2', 1786466848, 'pager')") conn.execute("INSERT INTO wifi_device (hash, scan, mac, time, signal, freq, packets) VALUES (1, 1, 'AE77C0EB3141', 1786466531, -71, 2412, 5)") conn.execute("INSERT INTO wifi_device (hash, scan, mac, time, signal, freq, packets) VALUES (2, 1, 'C89E43648080', 1786466532, -76, 5745, 9)") conn.execute("INSERT INTO ssid (hash, wifi_device, scan, type, bssid, ssid, hidden, time, signal, freq, channel, encryption) " "VALUES (10, 2, 1, 8, 'C89E43648080', X'416E646572736F6E2D35', 0, 1786466532, -76, 5745, 149, 0x400400108)") conn.execute("INSERT INTO ssid (hash, wifi_device, scan, type, bssid, ssid, hidden, time, signal, freq, channel, encryption) " "VALUES (11, 2, 1, 8, '506F9A010000', X'', 1, 1786466532, -64, 5745, 149, 0)") conn.execute("INSERT INTO ssid (hash, wifi_device, scan, type, bssid, ssid, hidden, time, signal, freq, channel, encryption) " "VALUES (12, 1, 1, 4, NULL, X'5A6E6574', NULL, 1786466531, -40, 2412, NULL, NULL)") conn.execute("INSERT INTO handshake (hash, scan, stahash, aphash, time) VALUES (20, 1, 1, 2, 1786466600)") conn.execute("INSERT INTO hostap_basic (scan, time, type, identity, password, verified) VALUES (1, 1786466601, 'WPA', 'bob', '', 0)") conn.commit() conn.close() return db class DecodersTest(unittest.TestCase): def test_fmt_mac_colon_form(self): self.assertEqual(server.fmt_mac('AE77C0EB3141'), 'AE:77:C0:EB:31:41') def test_fmt_mac_noop(self): self.assertEqual(server.fmt_mac('AE:77:C0:EB:31:41'), 'AE:77:C0:EB:31:41') self.assertEqual(server.fmt_mac(''), '--') self.assertEqual(server.fmt_mac(None), '--') def test_norm_mac_12hex(self): self.assertEqual(server._norm_mac('C89E43648080'), 'C8:9E:43:64:80:80') def test_decode_ssid_bytes_and_str(self): self.assertEqual(server.decode_ssid(b'Znet'), 'Znet') self.assertEqual(server.decode_ssid('Znet'), 'Znet') self.assertEqual(server.decode_ssid(None), '') self.assertEqual(server.decode_ssid(b'\xff\xfeZnet'), '\ufffd\ufffdZnet') def test_decode_ssid_cli_escapes(self): # Device sqlite3 CLI -json emits \xNN escapes for non-UTF8 BLOBs. self.assertEqual(server.decode_ssid('casaalicia\\x00.\\xde_;'), 'casaalicia\x00.\ufffd_;') def test_decode_encryption_cases(self): self.assertEqual(server.decode_encryption(0), 'Open') self.assertEqual(server.decode_encryption(None), 'Open') self.assertEqual(server.decode_encryption(2), 'WEP') self.assertEqual(server.decode_encryption(0x04), 'WPA') self.assertEqual(server.decode_encryption(0x08), 'WPA2') self.assertEqual(server.decode_encryption(0x04 | 0x08), 'WPA2 WPA') self.assertEqual(server.decode_encryption(0x400400108), 'WPA3 WPA2') self.assertEqual(server.decode_encryption(0x20050004C), 'WPA2 WPA') class ReconDataTest(unittest.TestCase): def setUp(self): self.db = make_db() server.RECON_DB = self.db def tearDown(self): os.unlink(self.db) def test_scans_list_uses_time_and_counts(self): data = server.recon_scans_data() self.assertEqual(len(data['scans']), 2) newest = data['scans'][0] self.assertEqual(newest['id'], 2) self.assertEqual(newest['time'], 1786466848) self.assertEqual(newest['name'], 'pager') old = data['scans'][1] self.assertEqual(old['devices'], 2) self.assertEqual(old['aps'], 2) self.assertEqual(old['handshakes'], 1) self.assertNotIn('uuid', old) def test_scan_detail_decodes_aps(self): data = server.recon_scan_data(1) self.assertEqual(data['scan']['id'], 1) self.assertEqual(data['scan']['time'], 1786466531) self.assertEqual(len(data['aps']), 2) aps = {a['bssid']: a for a in data['aps']} a = aps['C8:9E:43:64:80:80'] self.assertEqual(a['ssid'], 'Anderson-5') self.assertEqual(a['channel'], 149) self.assertEqual(a['signal'], -76) self.assertEqual(a['encryption'], 'WPA3 WPA2') self.assertFalse(a['hidden']) hidden = aps['50:6F:9A:01:00:00'] self.assertTrue(hidden['hidden']) self.assertEqual(hidden['encryption'], 'Open') def test_scan_detail_clients_exclude_ap_macs(self): data = server.recon_scan_data(1) macs = [c['mac'] for c in data['clients']] self.assertEqual(macs, ['AE:77:C0:EB:31:41']) def test_scan_detail_handshakes_resolve_macs(self): data = server.recon_scan_data(1) self.assertEqual(len(data['handshakes']), 1) hs = data['handshakes'][0] self.assertEqual(hs['ap'], 'C8:9E:43:64:80:80') self.assertEqual(hs['client'], 'AE:77:C0:EB:31:41') self.assertEqual(hs['time'], 1786466600) def test_scan_detail_missing_returns_none(self): self.assertIsNone(server.recon_scan_data(999)) class FakeSock: def __init__(self, resp=b''): self.resp = resp self.sent = b'' self.connected = None def settimeout(self, t): pass def connect(self, addr): self.connected = addr def sendall(self, b): self.sent += b def recv(self, n): chunk, self.resp = self.resp[:n], self.resp[n:] return chunk def close(self): pass class DaemonSockTest(unittest.TestCase): def test_socket_call_posts_json_to_sock(self): server.DAEMON_SOCK = '/tmp/api.sock' fake = FakeSock(b'HTTP/1.1 200 OK\r\nContent-Length: 17\r\n\r\n{"success":true}') with mock.patch.object(server.socket, 'socket', lambda *a, **k: fake): status, data = server.daemon_sock_call('POST', '/api/pineap/recon/new', body={'x': 1}) self.assertEqual(status, 200) self.assertEqual(data, {'success': True}) self.assertEqual(fake.connected, '/tmp/api.sock') self.assertTrue(fake.sent.startswith(b'POST /api/pineap/recon/new HTTP/1.1')) self.assertTrue(b'Content-Length: 8' in fake.sent) self.assertTrue(fake.sent.endswith(b'{"x": 1}')) def test_socket_call_connect_error(self): def boom(*a, **k): raise OSError('nope') with mock.patch.object(server.socket, 'socket', boom): status, data = server.daemon_sock_call('POST', '/api/pineap/recon/new') self.assertEqual(status, 0) self.assertIsNone(data) def test_start_stop_handlers_call_socket(self): calls = [] server.daemon_sock_call = lambda m, p, body=None: calls.append((m, p, body)) or (200, {'success': True}) server.h_recon_start(type('C', (), {'args': ()})()) server.h_recon_stop(type('C', (), {'args': ()})()) self.assertEqual(calls[0], ('POST', '/api/pineap/log/recon/start', {})) self.assertEqual(calls[1], ('POST', '/api/pineap/log/recon/stop', {})) def test_start_forwards_scan_time(self): calls = [] server.daemon_sock_call = lambda m, p, body=None: calls.append((m, p, body)) or (200, {'success': True}) ctx = type('C', (), {'args': (), 'body': {'scan_time': 60}})() status, data = server.h_recon_start(ctx) self.assertEqual(status, 200) self.assertEqual(calls[0], ('POST', '/api/pineap/log/recon/start', {'scan_time': 60})) def test_start_defaults_empty_body(self): calls = [] server.daemon_sock_call = lambda m, p, body=None: calls.append((m, p, body)) or (200, {'success': True}) server.h_recon_start(type('C', (), {'args': ()})()) self.assertEqual(calls[0], ('POST', '/api/pineap/log/recon/start', {})) class ReconScanStateTest(unittest.TestCase): """The daemon ignores scan_time and scans continuously until 'stop'. The webui must track the requested duration itself so timed scans actually end and the toggle can reflect real scan state.""" def setUp(self): self.db = make_db() server.RECON_DB = self.db server._recon_scan_state = {'active': False, 'started': 0, 'duration': 0} def tearDown(self): os.unlink(self.db) def _start(self, scan_time=None, fail=False): body = {} if scan_time is not None: body['scan_time'] = scan_time server.daemon_sock_call = (lambda m, p, body=None: (502, {})) if fail \ else (lambda m, p, body=None: (200, {'success': True})) return server.h_recon_start(type('C', (), {'args': (), 'body': body})()) def _stop(self, fail=False): server.daemon_sock_call = (lambda m, p, body=None: (502, {})) if fail \ else (lambda m, p, body=None: (200, {'success': True})) return server.h_recon_stop(type('C', (), {'args': ()})()) def _status(self): server.daemon_sock_call = lambda m, p, body=None: (200, {'success': True}) return server.h_recon_status(type('C', (), {'args': ()})()) def test_timed_start_marks_scanning_with_remaining(self): server.time.time = lambda: 1000.0 status, data = self._start(scan_time=30) self.assertEqual(status, 200) status, data = self._status() self.assertTrue(data['scanning']) self.assertEqual(data['scan_remaining'], 30) def test_timed_scan_expires_when_duration_elapses(self): base = [1000.0] server.time.time = lambda: base[0] self._start(scan_time=30) base[0] = 1031.0 status, data = self._status() self.assertFalse(data['scanning']) self.assertEqual(data['scan_remaining'], 0) def test_continuous_scan_has_no_remaining(self): server.time.time = lambda: 1000.0 self._start(scan_time=0) status, data = self._status() self.assertTrue(data['scanning']) self.assertIsNone(data['scan_remaining']) def test_default_start_is_continuous(self): server.time.time = lambda: 1000.0 self._start() status, data = self._status() self.assertTrue(data['scanning']) self.assertIsNone(data['scan_remaining']) def test_stop_clears_scanning(self): server.time.time = lambda: 1000.0 self._start(scan_time=30) self._stop() status, data = self._status() self.assertFalse(data['scanning']) def test_start_failure_does_not_mark_scanning(self): server.time.time = lambda: 1000.0 status, data = self._start(scan_time=30, fail=True) self.assertEqual(status, 502) status, data = self._status() self.assertFalse(data['scanning']) def test_watchdog_stops_expired_timed_scan(self): calls = [] server.time.time = lambda: 1000.0 self._start(scan_time=10) server.time.time = lambda: 1012.0 server.daemon_sock_call = lambda m, p, body=None: calls.append((m, p)) or (200, {'success': True}) server._recon_watchdog_tick() self.assertEqual(calls, [('POST', '/api/pineap/log/recon/stop')]) self.assertFalse(server._recon_scan_state['active']) def test_watchdog_leaves_active_scan_alone(self): server.time.time = lambda: 1000.0 self._start(scan_time=30) server.time.time = lambda: 1010.0 calls = [] server.daemon_sock_call = lambda m, p, body=None: calls.append((m, p)) or (200, {'success': True}) server._recon_watchdog_tick() self.assertEqual(calls, []) self.assertTrue(server._recon_scan_state['active']) class ReconExtrasTest(unittest.TestCase): def setUp(self): self.db = make_db() server.RECON_DB = self.db def tearDown(self): os.unlink(self.db) def test_status_reports_last_scan_and_active(self): server.time.time = lambda: 1786466532 + 100 status, data = server.h_recon_status(type('C', (), {'args': ()})()) self.assertEqual(status, 200) self.assertEqual(data['last_scan'], 1786466848) self.assertEqual(data['last_activity'], 1786466532) self.assertTrue(data['active']) server.time.time = lambda: 1786466532 + 1000 status, data = server.h_recon_status(type('C', (), {'args': ()})()) self.assertFalse(data['active']) def test_delete_cascades(self): server.recon_delete_scan(1) rows = server._db_rows(self.db, 'SELECT count(*) AS c FROM wifi_device') self.assertEqual(rows[0]['c'], 0) rows = server._db_rows(self.db, 'SELECT count(*) AS c FROM ssid') self.assertEqual(rows[0]['c'], 0) rows = server._db_rows(self.db, 'SELECT count(*) AS c FROM handshake') self.assertEqual(rows[0]['c'], 0) rows = server._db_rows(self.db, 'SELECT count(*) AS c FROM scan') self.assertEqual(rows[0]['c'], 1) def test_delete_handler_404_for_missing(self): status, data = server.h_recon_delete(type('C', (), {'args': ('999',)})()) self.assertEqual(status, 404) def test_events_lists_db_rows(self): status, data = server.h_recon_events(type('C', (), {'args': ()})()) self.assertEqual(status, 200) kinds = [e['type'] for e in data['events']] self.assertIn('auth attempt', kinds) self.assertEqual(data['events'][0]['time'], 1786466601) class ReconExamineTest(unittest.TestCase): def test_examine_bssid_calls_hak5(self): calls = [] server.hak5 = lambda *args, **kw: calls.append(args) or '' ctx = type('C', (), {'args': (), 'body': {'bssid': 'AA:BB:CC:DD:EE:FF'}})() status, data = server.h_recon_examine(ctx) self.assertEqual(status, 200) self.assertEqual(calls[0], ('PINEAPPLE_EXAMINE_BSSID', 'AA:BB:CC:DD:EE:FF')) def test_examine_channel_calls_hak5(self): calls = [] server.hak5 = lambda *args, **kw: calls.append(args) or '' ctx = type('C', (), {'args': (), 'body': {'channel': 6}})() status, data = server.h_recon_examine(ctx) self.assertEqual(status, 200) self.assertEqual(calls[0], ('PINEAPPLE_EXAMINE_CHANNEL', '6')) def test_examine_requires_target(self): server.hak5 = lambda *args, **kw: '' ctx = type('C', (), {'args': (), 'body': {}})() status, data = server.h_recon_examine(ctx) self.assertEqual(status, 400) class HandshakeFileTest(unittest.TestCase): def test_file_download_decodes_name(self): import shutil d = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, d) server.LOOT_HS_DIR = d name = '00 11_scan.cap' with open(os.path.join(d, name), 'wb') as f: f.write(b'PCAPDATA') status, payload = server.h_handshake_file(type('C', (), {'args': ('00%2011_scan.cap',)})) self.assertEqual(status, 200) self.assertEqual(payload.data, b'PCAPDATA') self.assertEqual(payload.filename, name) @unittest.skipIf(os.name == 'nt', 'colons are not valid in filenames on Windows') def test_file_download_decodes_mac_colon_name(self): import shutil d = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, d) server.LOOT_HS_DIR = d name = '00:11:22:33:44:55_scan.cap' with open(os.path.join(d, name), 'wb') as f: f.write(b'PCAPDATA') encoded = '00%3A11%3A22%3A33%3A44%3A55_scan.cap' status, payload = server.h_handshake_file(type('C', (), {'args': (encoded,)})) self.assertEqual(status, 200) self.assertEqual(payload.data, b'PCAPDATA') self.assertEqual(payload.filename, name) def test_file_download_404_missing(self): import shutil d = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, d) server.LOOT_HS_DIR = d status, payload = server.h_handshake_file(type('C', (), {'args': ('nope.cap',)})) self.assertEqual(status, 404) class CliFallbackTest(unittest.TestCase): def setUp(self): self.db = make_db() server.RECON_DB = self.db self._orig_sqlite3 = server.sqlite3 self._orig_device_run = server.device_run server.sqlite3 = None server.device_run = lambda args, timeout=20: (0, self._run(args), '') def _run(self, args): # emulate `sqlite3 -json ` over an in-memory copy import sqlite3 as s3 import subprocess conn = s3.connect(self.db) try: cur = conn.execute(args[-1]) conn.commit() if cur.description: import json rows = cur.fetchall() cols = [d[0] for d in cur.description] objs = [] for row in rows: obj = {} for c, v in zip(cols, row): if isinstance(v, bytes): try: v = v.decode('utf-8') except Exception: v = ''.join('\\x%02x' % b for b in v) obj[c] = v objs.append(obj) return json.dumps(objs) return '' finally: conn.close() def tearDown(self): server.sqlite3 = self._orig_sqlite3 server.device_run = self._orig_device_run os.unlink(self.db) def test_delete_cascade_via_cli(self): server.recon_delete_scan(1) rows = server._db_rows(self.db, 'SELECT count(*) AS c FROM scan') self.assertEqual(rows[0]['c'], 1) def test_cli_ssid_escapes_decoded(self): # emulate the CLI emitting \xNN escapes for a non-UTF8 blob server.device_run = lambda args, timeout=20: (0, '[{"ssid": "casaalicia\\\\x00.\\\\xde_"}]', '') server.RECON_DB = '/nonexistent.db' self.assertEqual(server.decode_ssid('casaalicia\\x00.\\xde_'), 'casaalicia\x00.\ufffd_') def make_hs_db(): db = make_db() conn = sqlite3.connect(db) conn.execute( "INSERT INTO handshake (hash, scan, stahash, aphash, time, beacon, hs1, hs2, hs3, hs4) " "VALUES (21, 1, 1, 2, 1786466650, X'424541434F4E', X'01', X'02', X'03', X'04')") conn.commit() conn.close() return db class ParseHsFilenameTest(unittest.TestCase): def test_parse_full_pcap(self): p = server.parse_hs_filename('1786466650_C8:9E:43:64:80:80_AE:77:C0:EB:31:41_handshake.pcap') self.assertEqual(p['ts'], 1786466650) self.assertEqual(p['ap'], 'C8:9E:43:64:80:80') self.assertEqual(p['client'], 'AE:77:C0:EB:31:41') self.assertEqual(p['kind'], 'full') self.assertEqual(p['ext'], 'pcap') def test_parse_partial_and_incomplete(self): p = server.parse_hs_filename('1_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake_partial.22000') self.assertEqual(p['kind'], 'partial') self.assertEqual(p['ext'], '22000') p = server.parse_hs_filename('1_C8:9E:43:64:80:80_AE:77:C0:EB:31:41_handshake_incomplete.pcap') self.assertEqual(p['kind'], 'incomplete') self.assertEqual(p['ext'], 'pcap') def test_parse_dash_macs_and_no_ts(self): p = server.parse_hs_filename('C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.pcap') self.assertIsNone(p['ts']) self.assertEqual(p['ap'], 'C8:9E:43:64:80:80') self.assertEqual(p['client'], 'AE:77:C0:EB:31:41') def test_parse_unrecognized(self): self.assertIsNone(server.parse_hs_filename('random.cap')) self.assertIsNone(server.parse_hs_filename('notes.txt')) self.assertIsNone(server.parse_hs_filename('')) self.assertIsNone(server.parse_hs_filename('123_mac1_mac2_handshake')) class HandshakesDataTest(unittest.TestCase): def setUp(self): self.db = make_hs_db() self.dir = tempfile.mkdtemp() self.addCleanup(server.__dict__.update, {'RECON_DB': server.RECON_DB, 'LOOT_HS_DIR': server.LOOT_HS_DIR}) server.RECON_DB = self.db server.LOOT_HS_DIR = self.dir def tearDown(self): shutil.rmtree(self.dir) os.unlink(self.db) def _write(self, name, ts): path = os.path.join(self.dir, name) open(path, 'w').close() os.utime(path, (ts, ts)) def test_empty_dir_skips_db(self): with mock.patch.object(server, '_db_rows', side_effect=AssertionError('db should not be touched')): data = server.handshakes_data() self.assertEqual(data, {'files': [], 'handshakes': []}) def test_correlation_composes_full_record(self): self._write('1786466650_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.pcap', 1786466650) data = server.handshakes_data() self.assertEqual(len(data['files']), 1) hs = data['handshakes'][0] self.assertEqual(hs['mac'], 'C8:9E:43:64:80:80') self.assertEqual(hs['client'], 'AE:77:C0:EB:31:41') self.assertEqual(hs['source'], 'Recon') self.assertEqual(hs['type'], 'full') self.assertEqual(hs['extension'], 'pcap') self.assertEqual(hs['timestamp'], 1786466650) self.assertTrue(hs['in_db']) self.assertEqual(hs['part_mask'], 15) self.assertTrue(hs['beacon']) self.assertEqual(hs['name'], '1786466650_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.pcap') self.assertTrue(hs['file_exists']) self.assertEqual(hs['location'], os.path.join(server.LOOT_HS_DIR, hs['name'])) def test_file_not_in_db_has_question_mark_fields(self): self._write('1786467000_AA-BB-CC-DD-EE-FF_00-11-22-33-44-55_handshake.pcap', 1786467000) hs = server.handshakes_data()['handshakes'][0] self.assertFalse(hs['in_db']) self.assertEqual(hs['part_mask'], 0) self.assertFalse(hs['beacon']) self.assertEqual(hs['timestamp'], 1786467000) def test_unparseable_file_still_listed_with_placeholders(self): self._write('random.cap', 1786467005) hs = server.handshakes_data()['handshakes'][0] self.assertEqual(hs['mac'], '--') self.assertEqual(hs['client'], '--') self.assertFalse(hs['in_db']) self.assertEqual(hs['extension'], 'cap') class HandshakeRoutesTest(unittest.TestCase): def setUp(self): self.dir = tempfile.mkdtemp() self.addCleanup(server.__dict__.update, {'LOOT_HS_DIR': server.LOOT_HS_DIR}) server.LOOT_HS_DIR = self.dir def tearDown(self): shutil.rmtree(self.dir) def _write(self, name, data=b'data'): with open(os.path.join(self.dir, name), 'wb') as f: f.write(data) def test_location_returns_loot_dir(self): status, data = server.h_handshakes_location(type('C', (), {'args': ()})()) self.assertEqual(status, 200) self.assertEqual(data['location'], self.dir) def test_location_route_precedes_file_download(self): h, args = server.ROUTER.dispatch('GET', '/api/pineap/handshakes/location') self.assertIs(h, server.h_handshakes_location) def test_delete_all_removes_files(self): self._write('1_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.pcap') self._write('2_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.22000') status, data = server.h_handshakes_delete_all(type('C', (), {'args': ()})()) self.assertEqual(status, 200) self.assertEqual(data['files'], []) self.assertEqual(data['handshakes'], []) self.assertEqual(os.listdir(self.dir), []) def test_delete_all_empty_dir_is_ok(self): status, data = server.h_handshakes_delete_all(type('C', (), {'args': ()})()) self.assertEqual(status, 200) self.assertEqual(data['files'], []) def test_delete_all_skips_dotfiles(self): self._write('.hidden') self._write('1_C8-9E-43-64-80-80_AE-77-C0-EB-31-41_handshake.pcap') status, data = server.h_handshakes_delete_all(type('C', (), {'args': ()})()) self.assertEqual(status, 200) self.assertEqual(data['files'], []) self.assertEqual(data['handshakes'], []) self.assertEqual(os.listdir(self.dir), ['.hidden'])