diff --git a/.github/workflows/sync-tag-definitions.yml b/.github/workflows/sync-tag-definitions.yml index d48fef3..c8ff2d2 100644 --- a/.github/workflows/sync-tag-definitions.yml +++ b/.github/workflows/sync-tag-definitions.yml @@ -29,167 +29,11 @@ jobs: - name: Fetch tag type definitions from OpenEPaperLink id: fetch - run: | - python3 << 'PYEOF' - import urllib.request - import json - import re - - print("Fetching tag type files from OpenEPaperLink repository...") - - # Fetch the directory listing - url = "https://github.com/OpenEPaperLink/OpenEPaperLink/tree/master/resources/tagtypes" - headers = {'User-Agent': 'Mozilla/5.0'} - req = urllib.request.Request(url, headers=headers) - - try: - with urllib.request.urlopen(req, timeout=30) as response: - html = response.read().decode('utf-8') - json_files = re.findall(r'([0-9a-fA-F]+\.json)', html) - json_files = sorted(set(json_files)) - print(f"Found {len(json_files)} tag type files") - except Exception as e: - print(f"Error fetching file list: {e}") - exit(1) - - # Fetch all tag type definitions - tag_types = {} - errors = [] - - for filename in json_files: - url = f"https://raw.githubusercontent.com/OpenEPaperLink/OpenEPaperLink/master/resources/tagtypes/{filename}" - try: - with urllib.request.urlopen(url, timeout=10) as response: - data = json.loads(response.read().decode('utf-8')) - type_id = int(filename.replace('.json', ''), 16) - - # Extract only required fields - tag_types[type_id] = { - 'version': data.get('version'), - 'name': data.get('name'), - 'width': data.get('width'), - 'height': data.get('height'), - } - except Exception as e: - errors.append(f"Error fetching {filename}: {e}") - - if errors: - for error in errors: - print(error) - - print(f"Successfully fetched {len(tag_types)} tag type definitions") - - # Save to file for next step - with open('new_tag_types.json', 'w') as f: - json.dump(tag_types, f, indent=2) - - print("Tag types saved to new_tag_types.json") - PYEOF + run: python3 scripts/fetch_tag_types.py new_tag_types.json - name: Generate updated tag_types.py id: generate - run: | - python3 << 'PYEOF' - import json - import re - - # Load new tag types - with open('new_tag_types.json', 'r') as f: - new_tag_types = json.load(f) - - # Read current tag_types.py - with open('custom_components/opendisplay/tag_types.py', 'r') as f: - content = f.read() - - # Extract current fallback definitions - match = re.search(r'fallback_definitions = \{(.*?)\n \}', content, re.DOTALL) - if not match: - print("Error: Could not find fallback_definitions in tag_types.py") - exit(1) - - current_definitions = match.group(1) - - # Parse current definitions to dict - current_types = {} - for line in current_definitions.split('\n'): - match = re.match(r'\s+(\d+):', line) - if match: - type_id = int(match.group(1)) - current_types[type_id] = line.strip() - - print(f"Current definitions: {len(current_types)} types") - print(f"New definitions: {len(new_tag_types)} types") - - # Check if there are differences - changed = False - added = [] - removed = [] - modified = [] - - # Find added and modified - for type_id in sorted(new_tag_types.keys()): - if type_id not in current_types: - added.append(type_id) - changed = True - else: - # Compare values - new_line = f'{type_id}: {json.dumps(new_tag_types[str(type_id)])}' - if new_line not in current_types[type_id]: - modified.append(type_id) - changed = True - - # Find removed - for type_id in current_types: - if str(type_id) not in new_tag_types and type_id not in [int(k) for k in new_tag_types.keys()]: - removed.append(type_id) - changed = True - - # Generate new fallback_definitions content - lines = [] - for type_id in sorted([int(k) for k in new_tag_types.keys()]): - type_data = new_tag_types[str(type_id)] - line = f' {type_id}: {json.dumps(type_data)},' - lines.append(line) - - new_fallback = '\n'.join(lines) - - # Replace in content - new_content = re.sub( - r'(fallback_definitions = \{)\n.*?\n( \})', - r'\1\n' + new_fallback + '\n\2', - content, - flags=re.DOTALL - ) - - # Write updated file - with open('custom_components/opendisplay/tag_types.py', 'w') as f: - f.write(new_content) - - # Create summary - summary = [] - if added: - summary.append(f"Added: {len(added)} types ({', '.join(map(str, added[:5]))}{'...' if len(added) > 5 else ''})") - if removed: - summary.append(f"Removed: {len(removed)} types ({', '.join(map(str, removed[:5]))}{'...' if len(removed) > 5 else ''})") - if modified: - summary.append(f"Modified: {len(modified)} types ({', '.join(map(str, modified[:5]))}{'...' if len(modified) > 5 else ''})") - - if changed: - print("CHANGED=true") - print(f"SUMMARY={'|'.join(summary)}") - with open('CHANGES_SUMMARY.txt', 'w') as f: - f.write('\n'.join(summary)) - else: - print("CHANGED=false") - print("No changes detected") - - # Set output for GitHub Actions - import os - with open(os.environ['GITHUB_OUTPUT'], 'a') as f: - f.write(f"changed={'true' if changed else 'false'}\n") - if summary: - f.write(f"summary={'|'.join(summary)}\n") - PYEOF + run: python3 scripts/generate_tag_types.py new_tag_types.json - name: Create Pull Request if: steps.generate.outputs.changed == 'true' diff --git a/scripts/fetch_tag_types.py b/scripts/fetch_tag_types.py new file mode 100644 index 0000000..b891cc8 --- /dev/null +++ b/scripts/fetch_tag_types.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +"""Fetch tag type definitions from the OpenEPaperLink repository. + +Downloads all tag type JSON files from the OpenEPaperLink GitHub repository +and saves them as a consolidated JSON file for further processing. +""" + +import json +import re +import sys +import urllib.request + + +GITHUB_TREE_URL = ( + "https://github.com/OpenEPaperLink/OpenEPaperLink/tree/master/resources/tagtypes" +) +GITHUB_RAW_URL = ( + "https://raw.githubusercontent.com/OpenEPaperLink/OpenEPaperLink" + "/master/resources/tagtypes" +) + + +def fetch_file_list(): + """Fetch the list of tag type JSON files from the repository.""" + print("Fetching tag type files from OpenEPaperLink repository...") + headers = {"User-Agent": "Mozilla/5.0"} + req = urllib.request.Request(GITHUB_TREE_URL, headers=headers) + + with urllib.request.urlopen(req, timeout=30) as response: + html = response.read().decode("utf-8") + json_files = re.findall(r"([0-9a-fA-F]+\.json)", html) + json_files = sorted(set(json_files)) + print(f"Found {len(json_files)} tag type files") + return json_files + + +def fetch_tag_types(json_files): + """Fetch and parse all tag type definitions.""" + tag_types = {} + errors = [] + + for filename in json_files: + url = f"{GITHUB_RAW_URL}/{filename}" + try: + with urllib.request.urlopen(url, timeout=10) as response: + data = json.loads(response.read().decode("utf-8")) + type_id = int(filename.replace(".json", ""), 16) + + tag_types[type_id] = { + "version": data.get("version"), + "name": data.get("name"), + "width": data.get("width"), + "height": data.get("height"), + } + except Exception as e: + errors.append(f"Error fetching {filename}: {e}") + + if errors: + for error in errors: + print(error) + + print(f"Successfully fetched {len(tag_types)} tag type definitions") + return tag_types + + +def main(): + """Fetch tag type definitions and save to a JSON file.""" + output_file = sys.argv[1] if len(sys.argv) > 1 else "new_tag_types.json" + + try: + json_files = fetch_file_list() + except Exception as e: + print(f"Error fetching file list: {e}") + sys.exit(1) + + tag_types = fetch_tag_types(json_files) + + with open(output_file, "w") as f: + json.dump(tag_types, f, indent=2) + + print(f"Tag types saved to {output_file}") + + +if __name__ == "__main__": + main() diff --git a/scripts/generate_tag_types.py b/scripts/generate_tag_types.py new file mode 100644 index 0000000..a467bb3 --- /dev/null +++ b/scripts/generate_tag_types.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +"""Generate updated tag_types.py from fetched tag type definitions. + +Reads a JSON file of tag type definitions (produced by fetch_tag_types.py), +compares them against the current fallback definitions in tag_types.py, +and updates the file if there are changes. + +Sets GitHub Actions outputs for downstream workflow steps. +""" + +import json +import os +import re +import sys + + +TAG_TYPES_PATH = "custom_components/opendisplay/tag_types.py" +FALLBACK_PATTERN = re.compile( + r"( fallback_definitions = \{)\n(.*?)\n( \})", re.DOTALL +) +ENTRY_PATTERN = re.compile(r"\s+(\d+):") + + +def load_new_tag_types(input_file): + """Load new tag types from JSON, converting keys to integers.""" + with open(input_file, "r") as f: + raw = json.load(f) + return {int(k): v for k, v in raw.items()} + + +def parse_current_definitions(content): + """Extract current fallback definitions from tag_types.py content.""" + match = FALLBACK_PATTERN.search(content) + if not match: + print("Error: Could not find fallback_definitions in tag_types.py") + sys.exit(1) + + current_types = {} + for line in match.group(2).split("\n"): + m = ENTRY_PATTERN.match(line) + if m: + type_id = int(m.group(1)) + current_types[type_id] = line.strip() + + return current_types + + +def compute_changes(current_types, new_tag_types): + """Compute added, removed, and modified tag types.""" + added = [] + removed = [] + modified = [] + + for type_id in sorted(new_tag_types.keys()): + if type_id not in current_types: + added.append(type_id) + else: + new_line = f"{type_id}: {json.dumps(new_tag_types[type_id], ensure_ascii=False)}," + if new_line != current_types[type_id]: + modified.append(type_id) + + for type_id in sorted(current_types.keys()): + if type_id not in new_tag_types: + removed.append(type_id) + + return added, removed, modified + + +def generate_fallback_content(new_tag_types): + """Generate the new fallback_definitions dict content.""" + lines = [] + for type_id in sorted(new_tag_types.keys()): + type_data = new_tag_types[type_id] + line = f" {type_id}: {json.dumps(type_data, ensure_ascii=False)}," + lines.append(line) + return "\n".join(lines) + + +def update_tag_types_file(content, new_fallback): + """Replace fallback_definitions content in tag_types.py.""" + match = FALLBACK_PATTERN.search(content) + if not match: + print("Error: Could not find fallback_definitions in tag_types.py") + sys.exit(1) + + start = match.start(2) + end = match.end(2) + return content[:start] + new_fallback + content[end:] + + +def build_summary(added, removed, modified): + """Build a human-readable summary of changes.""" + summary = [] + if added: + ids = ", ".join(map(str, added[:5])) + suffix = "..." if len(added) > 5 else "" + summary.append(f"Added: {len(added)} types ({ids}{suffix})") + if removed: + ids = ", ".join(map(str, removed[:5])) + suffix = "..." if len(removed) > 5 else "" + summary.append(f"Removed: {len(removed)} types ({ids}{suffix})") + if modified: + ids = ", ".join(map(str, modified[:5])) + suffix = "..." if len(modified) > 5 else "" + summary.append(f"Modified: {len(modified)} types ({ids}{suffix})") + return summary + + +def set_github_output(changed, summary): + """Set GitHub Actions step outputs.""" + github_output = os.environ.get("GITHUB_OUTPUT") + if not github_output: + return + + with open(github_output, "a") as f: + f.write(f"changed={'true' if changed else 'false'}\n") + if summary: + f.write(f"summary={'|'.join(summary)}\n") + + +def main(): + """Generate updated tag_types.py from fetched definitions.""" + input_file = sys.argv[1] if len(sys.argv) > 1 else "new_tag_types.json" + + new_tag_types = load_new_tag_types(input_file) + + with open(TAG_TYPES_PATH, "r") as f: + content = f.read() + + current_types = parse_current_definitions(content) + + print(f"Current definitions: {len(current_types)} types") + print(f"New definitions: {len(new_tag_types)} types") + + added, removed, modified = compute_changes(current_types, new_tag_types) + changed = bool(added or removed or modified) + + new_fallback = generate_fallback_content(new_tag_types) + new_content = update_tag_types_file(content, new_fallback) + + with open(TAG_TYPES_PATH, "w") as f: + f.write(new_content) + + summary = build_summary(added, removed, modified) + + if changed: + print("CHANGED=true") + print(f"SUMMARY={'|'.join(summary)}") + else: + print("CHANGED=false") + print("No changes detected") + + set_github_output(changed, summary) + + +if __name__ == "__main__": + main() diff --git a/tests/drawcustom/arc_test.py b/tests/drawcustom/arc_test.py index 21c23c2..beb052f 100644 --- a/tests/drawcustom/arc_test.py +++ b/tests/drawcustom/arc_test.py @@ -31,7 +31,7 @@ async def test_arc_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(ARC_IMG_PATH, 'arc_basic.png')) + example_img = Image.open(os.path.join(ARC_IMG_PATH, 'arc_basic.jpg')) assert images_equal(generated_img, example_img), "Basic arc rendering failed" @pytest.mark.asyncio @@ -56,5 +56,5 @@ async def test_pie_slice_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(ARC_IMG_PATH, 'pie_slice_basic.png')) + example_img = Image.open(os.path.join(ARC_IMG_PATH, 'pie_slice_basic.jpg')) assert images_equal(generated_img, example_img), "Basic pie slice rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/circle_test.py b/tests/drawcustom/circle_test.py index ffe8154..0a13080 100644 --- a/tests/drawcustom/circle_test.py +++ b/tests/drawcustom/circle_test.py @@ -31,7 +31,7 @@ async def test_circle_filled(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_filled.png')) + example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_filled.jpg')) assert images_equal(generated_img, example_img), "Basic filled circle rendering failed" @pytest.mark.asyncio @@ -55,7 +55,7 @@ async def test_circle_outline(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_outline.png')) + example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_outline.jpg')) assert images_equal(generated_img, example_img), "Basic outline circle rendering failed" @pytest.mark.asyncio @@ -80,5 +80,5 @@ async def test_circle_percentage(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_percentage.png')) + example_img = Image.open(os.path.join(CIRCLE_IMG_PATH, 'circle_percentage.jpg')) assert images_equal(generated_img, example_img), "Basic filled circle rendering failed" diff --git a/tests/drawcustom/common_test.py b/tests/drawcustom/common_test.py index a991340..ec55a69 100644 --- a/tests/drawcustom/common_test.py +++ b/tests/drawcustom/common_test.py @@ -30,7 +30,7 @@ async def test_multiple_elements(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'multiple_elements.png')) + example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'multiple_elements.jpg')) assert images_equal(generated_img, example_img), "Multiple elements drawing failed" @pytest.mark.asyncio @@ -56,7 +56,7 @@ async def test_rotation(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'rotated.png')) + example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'rotated.jpg')) assert images_equal(generated_img, example_img), "rotated elements drawing failed" @pytest.mark.asyncio @@ -76,7 +76,7 @@ async def test_oversize_elements(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'oversize_elements.png')) + example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'oversize_elements.jpg')) assert images_equal(generated_img, example_img), "Oversize elements drawing failed" @pytest.mark.asyncio @@ -97,7 +97,7 @@ async def test_overlapping_elements(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'overlapping_elements.png')) + example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'overlapping_elements.jpg')) assert images_equal(generated_img, example_img), "Overlapping elements drawing failed" @pytest.mark.asyncio @@ -117,5 +117,5 @@ async def test_negative_coordinates(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'negative_coordinates.png')) + example_img = Image.open(os.path.join(COMMON_IMG_PATH, 'negative_coordinates.jpg')) assert images_equal(generated_img, example_img), "Negative coordinate elements drawing failed" diff --git a/tests/drawcustom/conftest.py b/tests/drawcustom/conftest.py index f53cb65..cdeacc7 100644 --- a/tests/drawcustom/conftest.py +++ b/tests/drawcustom/conftest.py @@ -167,7 +167,7 @@ def images_equal(img1, img2): def save_image(image_bytes): """Save image for debugging.""" - img_path = os.path.join(BASE_IMG_PATH, 'rename_me.png') + img_path = os.path.join(BASE_IMG_PATH, 'rename_me.jpg') with open(img_path, 'wb') as f: f.write(image_bytes) diff --git a/tests/drawcustom/debug_grid_test.py b/tests/drawcustom/debug_grid_test.py index 89d2075..dcbf9bf 100644 --- a/tests/drawcustom/debug_grid_test.py +++ b/tests/drawcustom/debug_grid_test.py @@ -26,7 +26,7 @@ async def test_debug_grid_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_basic.png')) + example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_basic.jpg')) assert images_equal(generated_img, example_img), "Basic debug grid rendering failed" @pytest.mark.asyncio @@ -47,7 +47,7 @@ async def test_debug_grid_custom_spacing(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_custom_spacing.png')) + example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_custom_spacing.jpg')) assert images_equal(generated_img, example_img), "Custom spacing debug grid rendering failed" @pytest.mark.asyncio @@ -71,7 +71,7 @@ async def test_debug_grid_solid(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_solid.png')) + example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_solid.jpg')) assert images_equal(generated_img, example_img), "Solid debug grid rendering failed" @pytest.mark.asyncio @@ -91,5 +91,5 @@ async def test_debug_grid_without_labels(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_without_labels.png')) + example_img = Image.open(os.path.join(DEBUG_GRID_IMG_PATH, 'debug_grid_without_labels.jpg')) assert images_equal(generated_img, example_img), "Debug grid without labels rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/ellipse_test.py b/tests/drawcustom/ellipse_test.py index 44d1e5d..beb2955 100644 --- a/tests/drawcustom/ellipse_test.py +++ b/tests/drawcustom/ellipse_test.py @@ -32,7 +32,7 @@ async def test_circle_ellipse(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing.png')) + example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing.jpg')) assert images_equal(generated_img, example_img), "Basic ellipse drawing failed" @pytest.mark.asyncio @@ -58,5 +58,5 @@ async def test_circle_ellipse_percentage(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing_percentage.png')) + example_img = Image.open(os.path.join(ELLIPSE_IMG_PATH, 'ellipse_drawing_percentage.jpg')) assert images_equal(generated_img, example_img), "Basic ellipse drawing failed" \ No newline at end of file diff --git a/tests/drawcustom/icon_test.py b/tests/drawcustom/icon_test.py index 69264b1..ee1edec 100644 --- a/tests/drawcustom/icon_test.py +++ b/tests/drawcustom/icon_test.py @@ -30,5 +30,5 @@ # # generated_img = Image.open(BytesIO(image_data)) # save_image(image_data) -# example_img = Image.open(os.path.join(ICON_IMG_PATH, 'icon_basic.png')) +# example_img = Image.open(os.path.join(ICON_IMG_PATH, 'icon_basic.jpg')) # assert images_equal(generated_img, example_img), "Basic icon rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/line_test.py b/tests/drawcustom/line_test.py index 12943d8..02a5a82 100644 --- a/tests/drawcustom/line_test.py +++ b/tests/drawcustom/line_test.py @@ -30,7 +30,7 @@ async def test_line_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_basic.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_basic.jpg')) assert images_equal(generated_img, example_img), "Basic line rendering failed" @pytest.mark.asyncio @@ -55,7 +55,7 @@ async def test_line_custom(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_custom.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'line_custom.jpg')) assert images_equal(generated_img, example_img), "Custom line rendering failed" @pytest.mark.asyncio @@ -80,7 +80,7 @@ async def test_dashed_line_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_basic.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_basic.jpg')) assert images_equal(generated_img, example_img), "Basic dashed line rendering failed" @pytest.mark.asyncio @@ -107,7 +107,7 @@ async def test_dashed_line_custom_lengths(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_custom_lengths.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_custom_lengths.jpg')) assert images_equal(generated_img, example_img), "Custom dashed line rendering failed" @pytest.mark.asyncio @@ -131,7 +131,7 @@ async def test_dashed_line_basic_vertical(image_gen, mock_tag_info): return_value=mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_vertical.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_vertical.jpg')) assert images_equal(generated_img, example_img), "Vertical dashed line rendering failed" @pytest.mark.asyncio @@ -158,5 +158,5 @@ async def test_dashed_line_diagonal(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_diagonal.png')) + example_img = Image.open(os.path.join(LINE_IMG_PATH, 'dashed_line_diagonal.jpg')) assert images_equal(generated_img, example_img), "Dashed line diagonal rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/polygon_test.py b/tests/drawcustom/polygon_test.py index af2eec1..2d7e7f0 100644 --- a/tests/drawcustom/polygon_test.py +++ b/tests/drawcustom/polygon_test.py @@ -26,7 +26,7 @@ async def test_polygon_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_basic.png')) + example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_basic.jpg')) assert images_equal(generated_img, example_img), "Basic polygon rendering failed" @pytest.mark.asyncio @@ -46,5 +46,5 @@ async def test_polygon_filled(image_gen, mock_tag_info): return_value=mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_filled.png')) + example_img = Image.open(os.path.join(POLYGON_IMG_PATH, 'polygon_filled.jpg')) assert images_equal(generated_img, example_img), "Filled polygon rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/progress_bar_test.py b/tests/drawcustom/progress_bar_test.py index 5c52abf..7443875 100644 --- a/tests/drawcustom/progress_bar_test.py +++ b/tests/drawcustom/progress_bar_test.py @@ -34,7 +34,7 @@ async def test_basic_progress_bar(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar.jpg')) assert images_equal(generated_img, example_img), "Basic progress bar drawing failed" @pytest.mark.asyncio @@ -62,7 +62,7 @@ async def test_progress_bar_zero_progress(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.jpg')) assert images_equal(generated_img, example_img), "Basic progress bar drawing failed" @pytest.mark.asyncio @@ -91,7 +91,7 @@ async def test_progress_bar_full(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.jpg')) assert images_equal(generated_img, example_img), "Full progress bar drawing failed" @pytest.mark.asyncio @@ -119,7 +119,7 @@ async def test_progress_bar_negative_progress(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_zero.jpg')) assert images_equal(generated_img, example_img), "Progress bar with negative percentage drawing failed" @pytest.mark.asyncio @@ -148,7 +148,7 @@ async def test_progress_bar_over_full(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_full.jpg')) assert images_equal(generated_img, example_img), "Over full progress bar drawing failed" @pytest.mark.asyncio @@ -175,5 +175,5 @@ async def test_basic_progress_bar_percentage(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_percentage.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'progress_bar_percentage.jpg')) assert images_equal(generated_img, example_img), "Basic progress bar with percentage drawing failed" \ No newline at end of file diff --git a/tests/drawcustom/qr_code_test.py b/tests/drawcustom/qr_code_test.py index b317d89..8262b33 100644 --- a/tests/drawcustom/qr_code_test.py +++ b/tests/drawcustom/qr_code_test.py @@ -32,7 +32,7 @@ async def test_basic_qr_code(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code.jpg')) assert images_equal(generated_img, example_img), "Basic qr code drawing failed" @pytest.mark.asyncio @@ -59,7 +59,7 @@ async def test_long_qr_code(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_long.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_long.jpg')) assert images_equal(generated_img, example_img), "Long qr code drawing failed" @pytest.mark.asyncio @@ -85,5 +85,5 @@ async def test_basic_qr_code_percentage(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_percentage.png')) + example_img = Image.open(os.path.join(QR_CODE_IMG_PATH, 'qr_code_percentage.jpg')) assert images_equal(generated_img, example_img), "Basic qr code drawing with percentage failed" \ No newline at end of file diff --git a/tests/drawcustom/rectangle_pattern_test.py b/tests/drawcustom/rectangle_pattern_test.py index e066e89..b775823 100644 --- a/tests/drawcustom/rectangle_pattern_test.py +++ b/tests/drawcustom/rectangle_pattern_test.py @@ -36,7 +36,7 @@ async def test_rectangle_pattern(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern.png')) + example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern.jpg')) assert images_equal(generated_img, example_img), "Rectangle pattern rendering failed" @pytest.mark.asyncio @@ -68,7 +68,7 @@ async def test_rectangle_pattern_rounded_corners(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern_rounded_corners.png')) + example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_pattern_rounded_corners.jpg')) assert images_equal(generated_img, example_img), "Rounded corner rectangle pattern rendering failed" @pytest.mark.asyncio @@ -98,5 +98,5 @@ async def test_rectangle_pattern(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.png')) + example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.jpg')) assert images_equal(generated_img, example_img), "Rounded corner rectangle pattern rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/rectangle_test.py b/tests/drawcustom/rectangle_test.py index 7491f4b..00a35e3 100644 --- a/tests/drawcustom/rectangle_test.py +++ b/tests/drawcustom/rectangle_test.py @@ -32,7 +32,7 @@ async def test_rectangle_filled(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_filled.png')) + example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_filled.jpg')) assert images_equal(generated_img, example_img), "Filled rectangle rendering failed" @pytest.mark.asyncio @@ -57,7 +57,7 @@ async def test_rectangle_outline(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_outline.png')) + example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_outline.jpg')) assert images_equal(generated_img, example_img), "Outlined rectangle rendering failed" @pytest.mark.asyncio @@ -85,5 +85,5 @@ async def test_rectangle_rounded_corners(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_rounded_corners.png')) + example_img = Image.open(os.path.join(RECTANGLE_IMG_PATH, 'rectangle_rounded_corners.jpg')) assert images_equal(generated_img, example_img), "Rounded corner rectangle rendering failed" \ No newline at end of file diff --git a/tests/drawcustom/test_images/arc/arc_basic.png b/tests/drawcustom/test_images/arc/arc_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/arc/arc_basic.png rename to tests/drawcustom/test_images/arc/arc_basic.jpg diff --git a/tests/drawcustom/test_images/arc/pie_slice_basic.png b/tests/drawcustom/test_images/arc/pie_slice_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/arc/pie_slice_basic.png rename to tests/drawcustom/test_images/arc/pie_slice_basic.jpg diff --git a/tests/drawcustom/test_images/blank.png b/tests/drawcustom/test_images/blank.jpg similarity index 100% rename from tests/drawcustom/test_images/blank.png rename to tests/drawcustom/test_images/blank.jpg diff --git a/tests/drawcustom/test_images/circle/circle_filled.png b/tests/drawcustom/test_images/circle/circle_filled.jpg similarity index 100% rename from tests/drawcustom/test_images/circle/circle_filled.png rename to tests/drawcustom/test_images/circle/circle_filled.jpg diff --git a/tests/drawcustom/test_images/circle/circle_outline.png b/tests/drawcustom/test_images/circle/circle_outline.jpg similarity index 100% rename from tests/drawcustom/test_images/circle/circle_outline.png rename to tests/drawcustom/test_images/circle/circle_outline.jpg diff --git a/tests/drawcustom/test_images/circle/circle_percentage.png b/tests/drawcustom/test_images/circle/circle_percentage.jpg similarity index 100% rename from tests/drawcustom/test_images/circle/circle_percentage.png rename to tests/drawcustom/test_images/circle/circle_percentage.jpg diff --git a/tests/drawcustom/test_images/common/multiple_elements.png b/tests/drawcustom/test_images/common/multiple_elements.jpg similarity index 100% rename from tests/drawcustom/test_images/common/multiple_elements.png rename to tests/drawcustom/test_images/common/multiple_elements.jpg diff --git a/tests/drawcustom/test_images/common/negative_coordinates.png b/tests/drawcustom/test_images/common/negative_coordinates.jpg similarity index 100% rename from tests/drawcustom/test_images/common/negative_coordinates.png rename to tests/drawcustom/test_images/common/negative_coordinates.jpg diff --git a/tests/drawcustom/test_images/common/overlapping_elements.jpg b/tests/drawcustom/test_images/common/overlapping_elements.jpg new file mode 100644 index 0000000..a8eab05 Binary files /dev/null and b/tests/drawcustom/test_images/common/overlapping_elements.jpg differ diff --git a/tests/drawcustom/test_images/common/overlapping_elements.png b/tests/drawcustom/test_images/common/overlapping_elements.png deleted file mode 100644 index 4c213b4..0000000 Binary files a/tests/drawcustom/test_images/common/overlapping_elements.png and /dev/null differ diff --git a/tests/drawcustom/test_images/common/oversize_elements.png b/tests/drawcustom/test_images/common/oversize_elements.jpg similarity index 100% rename from tests/drawcustom/test_images/common/oversize_elements.png rename to tests/drawcustom/test_images/common/oversize_elements.jpg diff --git a/tests/drawcustom/test_images/common/rotated.png b/tests/drawcustom/test_images/common/rotated.jpg similarity index 100% rename from tests/drawcustom/test_images/common/rotated.png rename to tests/drawcustom/test_images/common/rotated.jpg diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_basic.png b/tests/drawcustom/test_images/debug_grid/debug_grid_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/debug_grid/debug_grid_basic.png rename to tests/drawcustom/test_images/debug_grid/debug_grid_basic.jpg diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.png b/tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.jpg similarity index 100% rename from tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.png rename to tests/drawcustom/test_images/debug_grid/debug_grid_custom_spacing.jpg diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_solid.png b/tests/drawcustom/test_images/debug_grid/debug_grid_solid.jpg similarity index 100% rename from tests/drawcustom/test_images/debug_grid/debug_grid_solid.png rename to tests/drawcustom/test_images/debug_grid/debug_grid_solid.jpg diff --git a/tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.png b/tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.jpg similarity index 100% rename from tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.png rename to tests/drawcustom/test_images/debug_grid/debug_grid_without_labels.jpg diff --git a/tests/drawcustom/test_images/ellipse/ellipse_drawing.png b/tests/drawcustom/test_images/ellipse/ellipse_drawing.jpg similarity index 100% rename from tests/drawcustom/test_images/ellipse/ellipse_drawing.png rename to tests/drawcustom/test_images/ellipse/ellipse_drawing.jpg diff --git a/tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.png b/tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.jpg similarity index 100% rename from tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.png rename to tests/drawcustom/test_images/ellipse/ellipse_drawing_percentage.jpg diff --git a/tests/drawcustom/test_images/line/dashed_line_basic.png b/tests/drawcustom/test_images/line/dashed_line_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/line/dashed_line_basic.png rename to tests/drawcustom/test_images/line/dashed_line_basic.jpg diff --git a/tests/drawcustom/test_images/line/dashed_line_custom_lengths.png b/tests/drawcustom/test_images/line/dashed_line_custom_lengths.jpg similarity index 100% rename from tests/drawcustom/test_images/line/dashed_line_custom_lengths.png rename to tests/drawcustom/test_images/line/dashed_line_custom_lengths.jpg diff --git a/tests/drawcustom/test_images/line/dashed_line_diagonal.png b/tests/drawcustom/test_images/line/dashed_line_diagonal.jpg similarity index 100% rename from tests/drawcustom/test_images/line/dashed_line_diagonal.png rename to tests/drawcustom/test_images/line/dashed_line_diagonal.jpg diff --git a/tests/drawcustom/test_images/line/dashed_line_vertical.png b/tests/drawcustom/test_images/line/dashed_line_vertical.jpg similarity index 100% rename from tests/drawcustom/test_images/line/dashed_line_vertical.png rename to tests/drawcustom/test_images/line/dashed_line_vertical.jpg diff --git a/tests/drawcustom/test_images/line/line_basic.png b/tests/drawcustom/test_images/line/line_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/line/line_basic.png rename to tests/drawcustom/test_images/line/line_basic.jpg diff --git a/tests/drawcustom/test_images/line/line_custom.png b/tests/drawcustom/test_images/line/line_custom.jpg similarity index 100% rename from tests/drawcustom/test_images/line/line_custom.png rename to tests/drawcustom/test_images/line/line_custom.jpg diff --git a/tests/drawcustom/test_images/polygon/polygon_basic.png b/tests/drawcustom/test_images/polygon/polygon_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/polygon/polygon_basic.png rename to tests/drawcustom/test_images/polygon/polygon_basic.jpg diff --git a/tests/drawcustom/test_images/polygon/polygon_filled.png b/tests/drawcustom/test_images/polygon/polygon_filled.jpg similarity index 100% rename from tests/drawcustom/test_images/polygon/polygon_filled.png rename to tests/drawcustom/test_images/polygon/polygon_filled.jpg diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar.png b/tests/drawcustom/test_images/progress_bar/progress_bar.jpg similarity index 100% rename from tests/drawcustom/test_images/progress_bar/progress_bar.png rename to tests/drawcustom/test_images/progress_bar/progress_bar.jpg diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_full.png b/tests/drawcustom/test_images/progress_bar/progress_bar_full.jpg similarity index 100% rename from tests/drawcustom/test_images/progress_bar/progress_bar_full.png rename to tests/drawcustom/test_images/progress_bar/progress_bar_full.jpg diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_percentage.png b/tests/drawcustom/test_images/progress_bar/progress_bar_percentage.jpg similarity index 100% rename from tests/drawcustom/test_images/progress_bar/progress_bar_percentage.png rename to tests/drawcustom/test_images/progress_bar/progress_bar_percentage.jpg diff --git a/tests/drawcustom/test_images/progress_bar/progress_bar_zero.png b/tests/drawcustom/test_images/progress_bar/progress_bar_zero.jpg similarity index 100% rename from tests/drawcustom/test_images/progress_bar/progress_bar_zero.png rename to tests/drawcustom/test_images/progress_bar/progress_bar_zero.jpg diff --git a/tests/drawcustom/test_images/qr_code/qr_code.png b/tests/drawcustom/test_images/qr_code/qr_code.jpg similarity index 100% rename from tests/drawcustom/test_images/qr_code/qr_code.png rename to tests/drawcustom/test_images/qr_code/qr_code.jpg diff --git a/tests/drawcustom/test_images/qr_code/qr_code_long.png b/tests/drawcustom/test_images/qr_code/qr_code_long.jpg similarity index 100% rename from tests/drawcustom/test_images/qr_code/qr_code_long.png rename to tests/drawcustom/test_images/qr_code/qr_code_long.jpg diff --git a/tests/drawcustom/test_images/qr_code/qr_code_percentage.png b/tests/drawcustom/test_images/qr_code/qr_code_percentage.jpg similarity index 100% rename from tests/drawcustom/test_images/qr_code/qr_code_percentage.png rename to tests/drawcustom/test_images/qr_code/qr_code_percentage.jpg diff --git a/tests/drawcustom/test_images/rectangle/rectangle_filled.png b/tests/drawcustom/test_images/rectangle/rectangle_filled.jpg similarity index 100% rename from tests/drawcustom/test_images/rectangle/rectangle_filled.png rename to tests/drawcustom/test_images/rectangle/rectangle_filled.jpg diff --git a/tests/drawcustom/test_images/rectangle/rectangle_outline.png b/tests/drawcustom/test_images/rectangle/rectangle_outline.jpg similarity index 100% rename from tests/drawcustom/test_images/rectangle/rectangle_outline.png rename to tests/drawcustom/test_images/rectangle/rectangle_outline.jpg diff --git a/tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.png b/tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.jpg similarity index 100% rename from tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.png rename to tests/drawcustom/test_images/rectangle/rectangle_rounded_corners.jpg diff --git a/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.png b/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.jpg similarity index 100% rename from tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.png rename to tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern.jpg diff --git a/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.png b/tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.jpg similarity index 100% rename from tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.png rename to tests/drawcustom/test_images/rectangle_pattern/rectangle_pattern_rounded_corners.jpg diff --git a/tests/drawcustom/test_images/text/large_font.png b/tests/drawcustom/test_images/text/large_font.jpg similarity index 100% rename from tests/drawcustom/test_images/text/large_font.png rename to tests/drawcustom/test_images/text/large_font.jpg diff --git a/tests/drawcustom/test_images/text/small_font.png b/tests/drawcustom/test_images/text/small_font.jpg similarity index 100% rename from tests/drawcustom/test_images/text/small_font.png rename to tests/drawcustom/test_images/text/small_font.jpg diff --git a/tests/drawcustom/test_images/text/text_anchors.png b/tests/drawcustom/test_images/text/text_anchors.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_anchors.png rename to tests/drawcustom/test_images/text/text_anchors.jpg diff --git a/tests/drawcustom/test_images/text/text_basic.png b/tests/drawcustom/test_images/text/text_basic.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_basic.png rename to tests/drawcustom/test_images/text/text_basic.jpg diff --git a/tests/drawcustom/test_images/text/text_color_markup.png b/tests/drawcustom/test_images/text/text_color_markup.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_color_markup.png rename to tests/drawcustom/test_images/text/text_color_markup.jpg diff --git a/tests/drawcustom/test_images/text/text_mixed_fonts.png b/tests/drawcustom/test_images/text/text_mixed_fonts.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_mixed_fonts.png rename to tests/drawcustom/test_images/text/text_mixed_fonts.jpg diff --git a/tests/drawcustom/test_images/text/text_percentage.png b/tests/drawcustom/test_images/text/text_percentage.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_percentage.png rename to tests/drawcustom/test_images/text/text_percentage.jpg diff --git a/tests/drawcustom/test_images/text/text_special_chars.png b/tests/drawcustom/test_images/text/text_special_chars.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_special_chars.png rename to tests/drawcustom/test_images/text/text_special_chars.jpg diff --git a/tests/drawcustom/test_images/text/text_truncate.png b/tests/drawcustom/test_images/text/text_truncate.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_truncate.png rename to tests/drawcustom/test_images/text/text_truncate.jpg diff --git a/tests/drawcustom/test_images/text/text_wrapping.png b/tests/drawcustom/test_images/text/text_wrapping.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_wrapping.png rename to tests/drawcustom/test_images/text/text_wrapping.jpg diff --git a/tests/drawcustom/test_images/text/text_wrapping_anchor.png b/tests/drawcustom/test_images/text/text_wrapping_anchor.jpg similarity index 100% rename from tests/drawcustom/test_images/text/text_wrapping_anchor.png rename to tests/drawcustom/test_images/text/text_wrapping_anchor.jpg diff --git a/tests/drawcustom/test_images/text_multiline/multiline_empty_line.png b/tests/drawcustom/test_images/text_multiline/multiline_empty_line.jpg similarity index 100% rename from tests/drawcustom/test_images/text_multiline/multiline_empty_line.png rename to tests/drawcustom/test_images/text_multiline/multiline_empty_line.jpg diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline.png b/tests/drawcustom/test_images/text_multiline/text_multiline.jpg similarity index 100% rename from tests/drawcustom/test_images/text_multiline/text_multiline.png rename to tests/drawcustom/test_images/text_multiline/text_multiline.jpg diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.png b/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.jpg similarity index 100% rename from tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.png rename to tests/drawcustom/test_images/text_multiline/text_multiline_delimiter.jpg diff --git a/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.png b/tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.jpg similarity index 100% rename from tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.png rename to tests/drawcustom/test_images/text_multiline/text_multiline_delimiter_and_newline.jpg diff --git a/tests/drawcustom/text_multiline_test.py b/tests/drawcustom/text_multiline_test.py index 6ebbf8e..3b13f6b 100644 --- a/tests/drawcustom/text_multiline_test.py +++ b/tests/drawcustom/text_multiline_test.py @@ -32,7 +32,7 @@ async def test_text_multiline_basic(image_gen, mock_tag_info): generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline.png')) + example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline.jpg')) assert images_equal(generated_img, example_img), "Basic text rendering failed" @pytest.mark.asyncio @@ -59,7 +59,7 @@ async def test_text_multiline_delimiter(image_gen, mock_tag_info): generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter.png')) + example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter.jpg')) assert images_equal(generated_img, example_img), "Multiline text with delimiter rendering failed" @pytest.mark.asyncio @@ -85,7 +85,7 @@ async def test_text_multiline_empty_line(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_empty_line.png')) + example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_empty_line.jpg')) assert images_equal(generated_img, example_img), "Multiline text with empty line rendering failed" @pytest.mark.asyncio @@ -111,7 +111,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter_and_newline.png')) + example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'text_multiline_delimiter_and_newline.jpg')) assert images_equal(generated_img, example_img), "Multiline text with delimiter and newline rendering failed" # @pytest.mark.asyncio @@ -142,7 +142,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info): # # generated_img = Image.open(BytesIO(image_data)) # save_image(image_data) -# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'calendar_format.png')) +# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'calendar_format.jpg')) # assert images_equal(generated_img, example_img), "Calendar format multiline rendering failed" # # @pytest.mark.asyncio @@ -172,7 +172,7 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info): # ) # # generated_img = Image.open(BytesIO(image_data)) -# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_blank_lines.png')) +# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_blank_lines.jpg')) # assert images_equal(generated_img, example_img), "Multiline text with blank lines rendering failed" # # @pytest.mark.asyncio @@ -202,5 +202,5 @@ async def test_text_multiline_delimiter_and_newline(image_gen, mock_tag_info): # ) # # generated_img = Image.open(BytesIO(image_data)) -# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_whitespace.png')) +# example_img = Image.open(os.path.join(TEXT_MULTILINE_IMG_PATH, 'multiline_whitespace.jpg')) # assert images_equal(generated_img, example_img), "Multiline text whitespace handling failed" \ No newline at end of file diff --git a/tests/drawcustom/text_test.py b/tests/drawcustom/text_test.py index b278445..d64e6d6 100644 --- a/tests/drawcustom/text_test.py +++ b/tests/drawcustom/text_test.py @@ -31,7 +31,7 @@ async def test_text_basic(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.jpg')) assert images_equal(generated_img, example_img), "Basic text rendering failed" @@ -55,7 +55,7 @@ async def test_small_font_size(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'small_font.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'small_font.jpg')) assert images_equal(generated_img, example_img), "Small font size rendering failed" @@ -79,7 +79,7 @@ async def test_large_font_size(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'large_font.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'large_font.jpg')) assert images_equal(generated_img, example_img), "Large font size rendering failed" @@ -104,7 +104,7 @@ async def test_text_wrapping(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping.jpg')) assert images_equal(generated_img, example_img), "Text wrapping failed" async def test_text_wrapping_with_anchor(image_gen, mock_tag_info): @@ -130,7 +130,7 @@ async def test_text_wrapping_with_anchor(image_gen, mock_tag_info): generated_img = Image.open(BytesIO(image_data)) save_image(image_data) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping_anchor.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_wrapping_anchor.jpg')) assert images_equal(generated_img, example_img), "Text wrapping failed" @@ -161,7 +161,7 @@ async def test_text_with_special_characters(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_special_chars.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_special_chars.jpg')) assert images_equal(generated_img, example_img), "Special characters rendering failed" @@ -195,7 +195,7 @@ async def test_text_color_markup(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_color_markup.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_color_markup.jpg')) assert images_equal(generated_img, example_img), "Color markup rendering failed" @pytest.mark.asyncio @@ -220,7 +220,7 @@ async def test_text_percentage(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_percentage.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_percentage.jpg')) assert images_equal(generated_img, example_img), "Text with percentage rendering failed" # @pytest.mark.asyncio @@ -266,7 +266,7 @@ async def test_text_percentage(image_gen, mock_tag_info): # # generated_img = Image.open(BytesIO(image_data)) # save_image(image_data) -# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_alignment.png')) +# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_alignment.jpg')) # assert images_equal(generated_img, example_img), "Text alignment failed" @pytest.mark.asyncio @@ -311,7 +311,7 @@ async def test_text_anchors(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_anchors.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_anchors.jpg')) assert images_equal(generated_img, example_img), "Text anchor points failed" @pytest.mark.asyncio @@ -346,7 +346,7 @@ async def test_text_mixed_fonts(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_mixed_fonts.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_mixed_fonts.jpg')) assert images_equal(generated_img, example_img), "Mixed fonts rendering failed" @pytest.mark.asyncio @@ -370,7 +370,7 @@ async def test_text_empty_string(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.png')) + example_img = Image.open(os.path.join(BASE_IMG_PATH, 'blank.jpg')) assert images_equal(generated_img, example_img), "Empty text handling failed" async def test_text_truncate(image_gen, mock_tag_info): @@ -395,7 +395,7 @@ async def test_text_truncate(image_gen, mock_tag_info): image_data = await generate_test_image(image_gen, service_data) generated_img = Image.open(BytesIO(image_data)) - example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_truncate.png')) + example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_truncate.jpg')) assert images_equal(generated_img, example_img), "Text truncation failed" # @pytest.mark.asyncio @@ -445,5 +445,5 @@ async def test_text_truncate(image_gen, mock_tag_info): # ) # # generated_img = Image.open(BytesIO(image_data)) -# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.png')) +# example_img = Image.open(os.path.join(TEXT_IMG_PATH, 'text_basic.jpg')) # assert images_equal(generated_img, example_img), "Basic text rendering failed" \ No newline at end of file diff --git a/tests/scripts/test_sync_tag_types.py b/tests/scripts/test_sync_tag_types.py new file mode 100644 index 0000000..f35eeff --- /dev/null +++ b/tests/scripts/test_sync_tag_types.py @@ -0,0 +1,381 @@ +"""Tests for the tag type sync scripts.""" + +import json +import os +import re +import textwrap +from unittest.mock import MagicMock, patch + +import pytest + +# Add scripts directory to path so we can import the modules +import sys + +REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.join(REPO_ROOT, "scripts")) + +import fetch_tag_types +import generate_tag_types + + +# --------------------------------------------------------------------------- +# Fixtures +# --------------------------------------------------------------------------- + +SAMPLE_TAG_TYPES_PY = textwrap.dedent("""\ + class Foo: + def _load_fallback_types(self): + fallback_definitions = { + 0: {"version": 4, "name": "M2 1.54\\"", "width": 152, "height": 152}, + 1: {"version": 5, "name": "M2 2.9\\"", "width": 296, "height": 128}, + 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0}, + 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0}, + } + self._tag_types = { + type_id: TagType(type_id, data) for type_id, data in fallback_definitions.items() + } +""") + + +@pytest.fixture +def tag_types_file(tmp_path): + """Write a minimal tag_types.py and return its path.""" + p = tmp_path / "tag_types.py" + p.write_text(SAMPLE_TAG_TYPES_PY) + return p + + +@pytest.fixture +def new_types_json(tmp_path): + """Write a new_tag_types.json and return its path.""" + data = { + 0: {"version": 4, "name": 'M2 1.54"', "width": 152, "height": 152}, + 1: {"version": 5, "name": 'M2 2.9"', "width": 296, "height": 128}, + 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0}, + 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0}, + } + p = tmp_path / "new_tag_types.json" + p.write_text(json.dumps(data, indent=2)) + return p + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – load_new_tag_types +# --------------------------------------------------------------------------- + +class TestLoadNewTagTypes: + """Tests for loading and converting JSON tag types.""" + + def test_keys_are_integers(self, new_types_json): + """JSON string keys must be converted to integers.""" + result = generate_tag_types.load_new_tag_types(str(new_types_json)) + assert all(isinstance(k, int) for k in result.keys()) + + def test_values_preserved(self, new_types_json): + """Tag type data values must be preserved after loading.""" + result = generate_tag_types.load_new_tag_types(str(new_types_json)) + assert result[0]["name"] == 'M2 1.54"' + assert result[250]["width"] == 0 + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – parse_current_definitions +# --------------------------------------------------------------------------- + +class TestParseCurrentDefinitions: + """Tests for parsing fallback_definitions from tag_types.py.""" + + def test_parses_all_entries(self, tag_types_file): + """Should parse all entries from the fallback_definitions block.""" + content = tag_types_file.read_text() + result = generate_tag_types.parse_current_definitions(content) + assert len(result) == 4 + assert set(result.keys()) == {0, 1, 240, 250} + + def test_keys_are_integers(self, tag_types_file): + """Parsed keys must be integers.""" + content = tag_types_file.read_text() + result = generate_tag_types.parse_current_definitions(content) + assert all(isinstance(k, int) for k in result.keys()) + + def test_exits_on_missing_block(self): + """Should exit if fallback_definitions block is not found.""" + with pytest.raises(SystemExit): + generate_tag_types.parse_current_definitions("no such block here") + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – compute_changes +# --------------------------------------------------------------------------- + +class TestComputeChanges: + """Tests for computing diffs between current and new definitions.""" + + def test_no_changes(self): + """Identical data should produce no changes.""" + current = { + 0: '0: {"version": 4, "name": "Tag0", "width": 100, "height": 100},', + } + new = {0: {"version": 4, "name": "Tag0", "width": 100, "height": 100}} + added, removed, modified = generate_tag_types.compute_changes(current, new) + assert added == [] + assert removed == [] + assert modified == [] + + def test_added(self): + """New type IDs should be detected as added.""" + current = {} + new = {5: {"version": 1, "name": "New", "width": 10, "height": 10}} + added, removed, modified = generate_tag_types.compute_changes(current, new) + assert added == [5] + assert removed == [] + + def test_removed(self): + """Missing type IDs should be detected as removed.""" + current = { + 5: '5: {"version": 1, "name": "Old", "width": 10, "height": 10},', + } + new = {} + added, removed, modified = generate_tag_types.compute_changes(current, new) + assert removed == [5] + assert added == [] + + def test_modified(self): + """Changed values should be detected as modified.""" + current = { + 0: '0: {"version": 1, "name": "Tag0", "width": 100, "height": 100},', + } + new = {0: {"version": 2, "name": "Tag0", "width": 100, "height": 100}} + added, removed, modified = generate_tag_types.compute_changes(current, new) + assert modified == [0] + + def test_sorting(self): + """Results should be sorted numerically, not lexicographically.""" + current = {} + new = { + 100: {"version": 1, "name": "A", "width": 1, "height": 1}, + 2: {"version": 1, "name": "B", "width": 1, "height": 1}, + 17: {"version": 1, "name": "C", "width": 1, "height": 1}, + } + added, _, _ = generate_tag_types.compute_changes(current, new) + assert added == [2, 17, 100] + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – generate_fallback_content +# --------------------------------------------------------------------------- + +class TestGenerateFallbackContent: + """Tests for generating the fallback_definitions dict content.""" + + def test_format(self): + """Each line should have 12-space indent, type_id, JSON data, and trailing comma.""" + data = {0: {"version": 1, "name": "Tag", "width": 10, "height": 20}} + content = generate_tag_types.generate_fallback_content(data) + assert content.startswith(" 0:") + assert content.endswith(",") + + def test_sorted_numerically(self): + """Entries should be sorted by numeric type_id.""" + data = { + 100: {"version": 1, "name": "A", "width": 1, "height": 1}, + 2: {"version": 1, "name": "B", "width": 1, "height": 1}, + 17: {"version": 1, "name": "C", "width": 1, "height": 1}, + } + content = generate_tag_types.generate_fallback_content(data) + ids = [ + int(m.group(1)) + for line in content.split("\n") + if (m := re.match(r"\s+(\d+):", line)) + ] + assert ids == [2, 17, 100] + + def test_unicode_chars_preserved(self): + """Unicode characters should be preserved (not escaped) with ensure_ascii=False.""" + data = {240: {"version": 2, "name": "SLT\u2010EM007", "width": 0, "height": 0}} + content = generate_tag_types.generate_fallback_content(data) + # ensure_ascii=False preserves the actual Unicode character + assert "\u2010" in content + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – update_tag_types_file +# --------------------------------------------------------------------------- + +class TestUpdateTagTypesFile: + """Tests for replacing fallback_definitions in file content.""" + + def test_replaces_content(self, tag_types_file): + """The fallback block should be replaced with new content.""" + content = tag_types_file.read_text() + new_fallback = ' 999: {"version": 1, "name": "New", "width": 1, "height": 1},' + result = generate_tag_types.update_tag_types_file(content, new_fallback) + assert "999:" in result + # Old entries removed + assert "250:" not in result + + def test_preserves_surrounding_code(self, tag_types_file): + """Code around fallback_definitions should be unchanged.""" + content = tag_types_file.read_text() + new_fallback = ' 999: {"version": 1, "name": "New", "width": 1, "height": 1},' + result = generate_tag_types.update_tag_types_file(content, new_fallback) + assert "class Foo:" in result + assert "self._tag_types" in result + + def test_unicode_in_replacement(self, tag_types_file): + """Unicode escape sequences in replacement must not cause regex errors. + + This is the primary bug that was fixed: json.dumps() produces \\uXXXX + sequences which re.sub() would interpret as bad regex escapes. + """ + content = tag_types_file.read_text() + # This would fail with re.sub() because \u2010 is a bad regex escape + new_fallback = ' 240: {"version": 2, "name": "SLT\\u2010EM007", "width": 0, "height": 0},' + result = generate_tag_types.update_tag_types_file(content, new_fallback) + assert "\\u2010" in result + + def test_exits_on_missing_block(self): + """Should exit if fallback_definitions block is not found.""" + with pytest.raises(SystemExit): + generate_tag_types.update_tag_types_file("no such block", "replacement") + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – build_summary +# --------------------------------------------------------------------------- + +class TestBuildSummary: + """Tests for the human-readable change summary.""" + + def test_empty_on_no_changes(self): + assert generate_tag_types.build_summary([], [], []) == [] + + def test_added(self): + result = generate_tag_types.build_summary([1, 2], [], []) + assert len(result) == 1 + assert "Added: 2" in result[0] + + def test_truncated(self): + result = generate_tag_types.build_summary(list(range(10)), [], []) + assert "..." in result[0] + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – set_github_output +# --------------------------------------------------------------------------- + +class TestSetGithubOutput: + """Tests for writing GitHub Actions outputs.""" + + def test_writes_changed(self, tmp_path): + output_file = tmp_path / "output.txt" + output_file.write_text("") + with patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}): + generate_tag_types.set_github_output(True, ["Added: 1 types (5)"]) + content = output_file.read_text() + assert "changed=true" in content + assert "summary=" in content + + def test_no_op_without_env(self, tmp_path): + """Should not crash when GITHUB_OUTPUT is not set.""" + with patch.dict(os.environ, {}, clear=True): + generate_tag_types.set_github_output(False, []) # should not raise + + +# --------------------------------------------------------------------------- +# Tests for generate_tag_types – full main() integration +# --------------------------------------------------------------------------- + +class TestMainIntegration: + """Integration tests for the full generate_tag_types.main() flow.""" + + def test_no_change_run(self, tag_types_file, new_types_json, tmp_path): + """When data matches, output changed=false.""" + output_file = tmp_path / "output.txt" + output_file.write_text("") + with patch.object(generate_tag_types, "TAG_TYPES_PATH", str(tag_types_file)), \ + patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}), \ + patch("sys.argv", ["prog", str(new_types_json)]): + generate_tag_types.main() + assert "changed=false" in output_file.read_text() + + def test_added_type_run(self, tag_types_file, tmp_path): + """When a new type is added, output changed=true and file is updated.""" + data = { + 0: {"version": 4, "name": 'M2 1.54"', "width": 152, "height": 152}, + 1: {"version": 5, "name": 'M2 2.9"', "width": 296, "height": 128}, + 240: {"version": 2, "name": "SLT\u2010EM007 Segmented", "width": 0, "height": 0}, + 250: {"version": 1, "name": "ConfigMode", "width": 0, "height": 0}, + 999: {"version": 1, "name": "Brand New", "width": 100, "height": 200}, + } + json_file = tmp_path / "new.json" + json_file.write_text(json.dumps(data, indent=2)) + + output_file = tmp_path / "output.txt" + output_file.write_text("") + with patch.object(generate_tag_types, "TAG_TYPES_PATH", str(tag_types_file)), \ + patch.dict(os.environ, {"GITHUB_OUTPUT": str(output_file)}), \ + patch("sys.argv", ["prog", str(json_file)]): + generate_tag_types.main() + assert "changed=true" in output_file.read_text() + updated = tag_types_file.read_text() + assert "999:" in updated + assert "Brand New" in updated + + +# --------------------------------------------------------------------------- +# Tests for fetch_tag_types +# --------------------------------------------------------------------------- + +class TestFetchTagTypes: + """Tests for the fetch_tag_types module.""" + + def test_fetch_file_list(self): + """fetch_file_list should parse JSON filenames from HTML.""" + fake_html = '00.json 0A.json other.txt' + mock_response = MagicMock() + mock_response.read.return_value = fake_html.encode("utf-8") + mock_response.__enter__ = MagicMock(return_value=mock_response) + mock_response.__exit__ = MagicMock(return_value=False) + + with patch("urllib.request.urlopen", return_value=mock_response): + result = fetch_tag_types.fetch_file_list() + assert result == ["00.json", "0A.json"] + + def test_fetch_tag_types_parses_hex_ids(self): + """Filenames should be converted from hex to decimal type IDs.""" + fake_json = json.dumps({ + "version": 1, "name": "Test", "width": 100, "height": 50 + }).encode("utf-8") + + mock_response = MagicMock() + mock_response.read.return_value = fake_json + mock_response.__enter__ = MagicMock(return_value=mock_response) + mock_response.__exit__ = MagicMock(return_value=False) + + with patch("urllib.request.urlopen", return_value=mock_response): + result = fetch_tag_types.fetch_tag_types(["0A.json"]) + # 0x0A = 10 + assert 10 in result + assert result[10]["name"] == "Test" + + def test_fetch_tag_types_handles_errors(self): + """Errors fetching individual files should not crash the whole run.""" + with patch("urllib.request.urlopen", side_effect=Exception("Network error")): + result = fetch_tag_types.fetch_tag_types(["00.json"]) + assert result == {} + + def test_main_writes_json(self, tmp_path): + """main() should write fetched data to the output JSON file.""" + output = tmp_path / "out.json" + + with patch.object(fetch_tag_types, "fetch_file_list", return_value=["01.json"]), \ + patch.object(fetch_tag_types, "fetch_tag_types", return_value={ + 1: {"version": 1, "name": "X", "width": 10, "height": 10} + }), \ + patch("sys.argv", ["prog", str(output)]): + fetch_tag_types.main() + + data = json.loads(output.read_text()) + assert "1" in data # JSON keys are strings + assert data["1"]["name"] == "X"