#!/usr/bin/env python3
from operator import truediv
from flask import make_response, request, current_app
from flask import url_for
import logging
import time
import os
import threading
# Thread-safe global storage for test endpoint content
# Avoids filesystem cache issues in parallel tests
_test_endpoint_content_lock = threading.Lock()
_test_endpoint_content = {}
def write_test_file_and_sync(filepath, content, mode='w'):
"""
Write test data to file and ensure it's synced to disk.
Also stores in thread-safe global dict to bypass filesystem cache.
Critical for parallel tests where workers may read files immediately after write.
Without fsync(), data may still be in OS buffers when workers try to read,
causing race conditions where old data is seen.
Args:
filepath: Full path to file
content: Content to write (str or bytes)
mode: File mode ('w' for text, 'wb' for binary)
"""
# Convert content to bytes if needed
if isinstance(content, str):
content_bytes = content.encode('utf-8')
else:
content_bytes = content
# Store in thread-safe global dict for instant access
with _test_endpoint_content_lock:
_test_endpoint_content[os.path.basename(filepath)] = content_bytes
# Also write to file for compatibility
with open(filepath, mode) as f:
f.write(content)
f.flush() # Flush Python buffer to OS
os.fsync(f.fileno()) # Force OS to write to disk
def set_original_response(datastore_path, extra_title=''):
test_return_data = f"""
head title{extra_title}
Some initial text
Which is across multiple lines
So let's see what happens.
"""
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), test_return_data)
return None
def set_modified_response(datastore_path):
test_return_data = """
modified head title
Some initial text
which has this one new line
So let's see what happens.
"""
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), test_return_data)
return None
def set_longer_modified_response(datastore_path):
test_return_data = """
modified head title
Some initial text
which has this one new line
So let's see what happens.
So let's see what happens.
So let's see what happens.
So let's see what happens.
"""
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), test_return_data)
return None
def set_more_modified_response(datastore_path):
test_return_data = """
modified head title
Some initial text
which has this one new line
So let's see what happens.
Ohh yeah awesome
"""
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), test_return_data)
return None
def set_empty_text_response(datastore_path):
test_return_data = """"""
write_test_file_and_sync(os.path.join(datastore_path, "endpoint-content.txt"), test_return_data)
return None
def wait_for_notification_endpoint_output(datastore_path):
'''Apprise can take a few seconds to fire'''
#@todo - could check the apprise object directly instead of looking for this file
from os.path import isfile
notification_file = os.path.join(datastore_path, "notification.txt")
for i in range(1, 20):
time.sleep(1)
if isfile(notification_file):
return True
return False
# kinda funky, but works for now
def get_UUID_for_tag_name(client, name):
app_config = client.application.config.get('DATASTORE').data
for uuid, tag in app_config['settings']['application'].get('tags', {}).items():
if name == tag.get('title', '').lower().strip():
return uuid
return None
# kinda funky, but works for now
def extract_rss_token_from_UI(client):
return client.application.config.get('DATASTORE').data['settings']['application'].get('rss_access_token')
# import re
# res = client.get(
# url_for("watchlist.index"),
# )
# m = re.search('token=(.+?)"', str(res.data))
# token_key = m.group(1)
# return token_key.strip()
# kinda funky, but works for now
def extract_UUID_from_client(client):
import re
res = client.get(
url_for("watchlist.index"),
)
# {{api_key}}
m = re.search('edit/(.+?)[#"]', str(res.data))
uuid = m.group(1)
return uuid.strip()
def get_form_field(html, name):
"""Return the ,